diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 6ccfafa..c507246 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -488,9 +488,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "js-sys", @@ -1860,9 +1860,9 @@ dependencies = [ [[package]] name = "image" -version = "0.24.4" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8e4fb07cf672b1642304e731ef8a6a4c7891d67bb4fd4f5ce58cd6ed86803c" +checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" dependencies = [ "bytemuck", "byteorder", diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index e8ac49f..f622bb7 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -1,9 +1,10 @@ use crate::{ - core::Core, - data::{ClashInfo, Data, PrfItem, PrfOption, Profiles, Verge}, + config::*, + core::*, + feat, utils::{dirs, help}, }; -use crate::{log_if_err, ret_err, wrap_err}; +use crate::{ret_err, wrap_err}; use anyhow::Result; use serde_yaml::Mapping; use std::collections::{HashMap, VecDeque}; @@ -11,248 +12,189 @@ use sysproxy::Sysproxy; type CmdResult = Result; -/// get all profiles from `profiles.yaml` #[tauri::command] -pub fn get_profiles() -> CmdResult { - let global = Data::global(); - let profiles = global.profiles.lock(); +pub fn get_profiles() -> CmdResult { + let profiles = ProfilesN::global().config.lock(); Ok(profiles.clone()) } -/// manually exec enhanced profile #[tauri::command] -pub fn enhance_profiles() -> CmdResult { - let core = Core::global(); - wrap_err!(core.activate()) +pub async fn enhance_profiles() -> CmdResult { + wrap_err!(CoreManager::global().activate_config().await) } -/// import the profile from url -/// and save to `profiles.yaml` +#[deprecated] #[tauri::command] pub async fn import_profile(url: String, option: Option) -> CmdResult { let item = wrap_err!(PrfItem::from_url(&url, None, None, option).await)?; - - let global = Data::global(); - let mut profiles = global.profiles.lock(); + let mut profiles = ProfilesN::global().config.lock(); wrap_err!(profiles.append_item(item)) } -/// new a profile -/// append a temp profile item file to the `profiles` dir -/// view the temp profile file by using vscode or other editor #[tauri::command] pub async fn create_profile(item: PrfItem, file_data: Option) -> CmdResult { let item = wrap_err!(PrfItem::from(item, file_data).await)?; - - let global = Data::global(); - let mut profiles = global.profiles.lock(); + let mut profiles = ProfilesN::global().config.lock(); wrap_err!(profiles.append_item(item)) } -/// Update the profile #[tauri::command] pub async fn update_profile(index: String, option: Option) -> CmdResult { - let core = Core::global(); - wrap_err!(core.update_profile_item(index, option).await) + wrap_err!(ProfilesN::global().update_item(index, option).await) } -/// change the current profile #[tauri::command] pub fn select_profile(index: String) -> CmdResult { - let global = Data::global(); - let mut profiles = global.profiles.lock(); + let mut profiles = ProfilesN::global().config.lock(); wrap_err!(profiles.put_current(index))?; drop(profiles); - let core = Core::global(); - wrap_err!(core.activate()) + wrap_err!(tauri::async_runtime::block_on(async { + CoreManager::global().activate_config().await + })) } /// change the profile chain #[tauri::command] pub fn change_profile_chain(chain: Option>) -> CmdResult { - let global = Data::global(); - let mut profiles = global.profiles.lock(); + let mut profiles = ProfilesN::global().config.lock(); wrap_err!(profiles.put_chain(chain))?; drop(profiles); - let core = Core::global(); - wrap_err!(core.activate()) + wrap_err!(tauri::async_runtime::block_on(async { + CoreManager::global().activate_config().await + })) } -/// change the profile valid fields #[tauri::command] pub fn change_profile_valid(valid: Option>) -> CmdResult { - let global = Data::global(); - let mut profiles = global.profiles.lock(); + let mut profiles = ProfilesN::global().config.lock(); wrap_err!(profiles.put_valid(valid))?; drop(profiles); - let core = Core::global(); - wrap_err!(core.activate()) + wrap_err!(tauri::async_runtime::block_on(async { + CoreManager::global().activate_config().await + })) } -/// delete profile item #[tauri::command] pub fn delete_profile(index: String) -> CmdResult { - let global = Data::global(); - let mut profiles = global.profiles.lock(); + let mut profiles = ProfilesN::global().config.lock(); if wrap_err!(profiles.delete_item(index))? { drop(profiles); - let core = Core::global(); - log_if_err!(core.activate()); + wrap_err!(tauri::async_runtime::block_on(async { + CoreManager::global().activate_config().await + }))?; } Ok(()) } -/// patch the profile config #[tauri::command] pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult { - let global = Data::global(); - let mut profiles = global.profiles.lock(); + let mut profiles = ProfilesN::global().config.lock(); wrap_err!(profiles.patch_item(index, profile))?; drop(profiles); - // update cron task - let core = Core::global(); - let mut timer = core.timer.lock(); - wrap_err!(timer.refresh()) + wrap_err!(timer::Timer::global().refresh()) } -/// run vscode command to edit the profile #[tauri::command] pub fn view_profile(index: String) -> CmdResult { - let global = Data::global(); - let profiles = global.profiles.lock(); + let profiles = ProfilesN::global().config.lock(); let item = wrap_err!(profiles.get_item(&index))?; - let file = item.file.clone(); - if file.is_none() { - ret_err!("file is null"); - } - - let path = dirs::app_profiles_dir().join(file.unwrap()); + 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!("file not found"); + ret_err!("the file not found"); } wrap_err!(help::open_file(path)) } -/// read the profile item file data #[tauri::command] pub fn read_profile_file(index: String) -> CmdResult { - let global = Data::global(); - let profiles = global.profiles.lock(); + let profiles = ProfilesN::global().config.lock(); let item = wrap_err!(profiles.get_item(&index))?; let data = wrap_err!(item.read_file())?; Ok(data) } -/// save the profile item file data #[tauri::command] pub fn save_profile_file(index: String, file_data: Option) -> CmdResult { if file_data.is_none() { return Ok(()); } - let global = Data::global(); - let profiles = global.profiles.lock(); + let profiles = ProfilesN::global().config.lock(); let item = wrap_err!(profiles.get_item(&index))?; wrap_err!(item.save_file(file_data.unwrap())) } -/// get the clash core info from the state -/// the caller can also get the infomation by clash's api #[tauri::command] -pub fn get_clash_info() -> CmdResult { - let global = Data::global(); - let clash = global.clash.lock(); - Ok(clash.info.clone()) +pub fn get_clash_info() -> CmdResult { + Ok(ClashN::global().info.lock().clone()) } -/// get the runtime clash config mapping #[tauri::command] pub fn get_runtime_config() -> CmdResult> { - let core = Core::global(); - let rt = core.runtime.lock(); - Ok(rt.config.clone()) + Ok(CoreManager::global().runtime_config.lock().config.clone()) } -/// get the runtime clash config yaml string #[tauri::command] pub fn get_runtime_yaml() -> CmdResult> { - let core = Core::global(); - let rt = core.runtime.lock(); - Ok(rt.config_yaml.clone()) + Ok(CoreManager::global() + .runtime_config + .lock() + .config_yaml + .clone()) } -/// get the runtime config exists keys #[tauri::command] pub fn get_runtime_exists() -> CmdResult> { - let core = Core::global(); - let rt = core.runtime.lock(); - Ok(rt.exists_keys.clone()) + Ok(CoreManager::global() + .runtime_config + .lock() + .exists_keys + .clone()) } -/// get the runtime enhanced chain log #[tauri::command] pub fn get_runtime_logs() -> CmdResult>> { - let core = Core::global(); - let rt = core.runtime.lock(); - Ok(rt.chain_logs.clone()) + Ok(CoreManager::global() + .runtime_config + .lock() + .chain_logs + .clone()) } -/// 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) -> CmdResult { - let core = Core::global(); - wrap_err!(core.patch_clash(payload)) + wrap_err!(feat::patch_clash(payload)) } #[tauri::command] -pub fn get_verge_config() -> CmdResult { - let global = Data::global(); - let verge = global.verge.lock(); - Ok(verge.clone()) -} - -/// patch the verge config -/// this command only save the config and not responsible for other things -#[tauri::command] -pub fn patch_verge_config(payload: Verge) -> CmdResult { - let core = Core::global(); - wrap_err!(core.patch_verge(payload)) +pub fn get_verge_config() -> CmdResult { + Ok(VergeN::global().config.lock().clone()) } #[tauri::command] -pub fn update_hotkeys(hotkeys: Vec) -> CmdResult { - let core = Core::global(); - let mut hotkey = core.hotkey.lock(); - wrap_err!(hotkey.update(hotkeys)) +pub fn patch_verge_config(payload: IVerge) -> CmdResult { + wrap_err!(feat::patch_verge(payload)) } -/// change clash core #[tauri::command] pub fn change_clash_core(clash_core: Option) -> CmdResult { - let core = Core::global(); - wrap_err!(core.change_core(clash_core)) + wrap_err!(tauri::async_runtime::block_on(async { + CoreManager::global().change_core(clash_core).await + })) } /// restart the sidecar #[tauri::command] pub fn restart_sidecar() -> CmdResult { - let core = Core::global(); - wrap_err!(core.restart_clash()) -} - -/// kill all sidecars when update app -#[tauri::command] -pub fn kill_sidecar() { - tauri::api::process::kill_children(); + wrap_err!(CoreManager::global().run_core()) } /// get the system proxy @@ -273,44 +215,38 @@ pub fn get_sys_proxy() -> CmdResult { #[tauri::command] pub fn get_clash_logs() -> CmdResult> { - let core = Core::global(); - let service = core.service.lock(); - Ok(service.get_logs()) + Ok(logger::Logger::global().get_log()) } -/// open app config dir #[tauri::command] pub fn open_app_dir() -> CmdResult<()> { let app_dir = dirs::app_home_dir(); wrap_err!(open::that(app_dir)) } -/// open logs dir #[tauri::command] pub fn open_logs_dir() -> CmdResult<()> { let log_dir = dirs::app_logs_dir(); wrap_err!(open::that(log_dir)) } -/// open url #[tauri::command] pub fn open_web_url(url: String) -> CmdResult<()> { wrap_err!(open::that(url)) } -/// service mode #[cfg(windows)] pub mod service { use super::*; use crate::core::win_service::JsonResponse; #[tauri::command] - pub async fn start_service() -> CmdResult<()> { + pub async fn start_service() -> CmdResult { wrap_err!(crate::core::Service::start_service().await) } #[tauri::command] - pub async fn stop_service() -> CmdResult<()> { + pub async fn stop_service() -> CmdResult { wrap_err!(crate::core::Service::stop_service().await) } @@ -324,12 +260,12 @@ pub mod service { } #[tauri::command] - pub async fn install_service() -> CmdResult<()> { + pub async fn install_service() -> CmdResult { wrap_err!(crate::core::Service::install_service().await) } #[tauri::command] - pub async fn uninstall_service() -> CmdResult<()> { + pub async fn uninstall_service() -> CmdResult { wrap_err!(crate::core::Service::uninstall_service().await) } } @@ -339,23 +275,23 @@ pub mod service { use super::*; #[tauri::command] - pub async fn start_service() -> CmdResult<()> { + pub async fn start_service() -> CmdResult { Ok(()) } #[tauri::command] - pub async fn stop_service() -> CmdResult<()> { + pub async fn stop_service() -> CmdResult { Ok(()) } #[tauri::command] - pub async fn check_service() -> CmdResult<()> { + pub async fn check_service() -> CmdResult { Ok(()) } #[tauri::command] - pub async fn install_service() -> CmdResult<()> { + pub async fn install_service() -> CmdResult { Ok(()) } #[tauri::command] - pub async fn uninstall_service() -> CmdResult<()> { + pub async fn uninstall_service() -> CmdResult { Ok(()) } } diff --git a/src-tauri/src/config/clash.rs b/src-tauri/src/config/clash.rs new file mode 100644 index 0000000..e498a34 --- /dev/null +++ b/src-tauri/src/config/clash.rs @@ -0,0 +1,180 @@ +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::sync::Arc; + +#[derive(Debug)] +pub struct ClashN { + /// maintain the clash config + pub config: Arc>, + /// some info + pub info: Arc>, +} + +impl ClashN { + pub fn global() -> &'static ClashN { + static DATA: OnceCell = 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 { + 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(); + + 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() { + config.insert(key, value); + } + + if change_info { + let mut info = self.info.lock(); + *info = ClashInfoN::from(&*config); + } + + self.save_config() + } +} + +#[derive(Default, Debug, Clone, Deserialize, Serialize)] +pub struct ClashInfoN { + /// clash sidecar status + pub status: String, + /// clash core port + pub port: Option, + /// same as `external-controller` + pub server: Option, + /// clash secret + pub secret: Option, +} + +impl ClashInfoN { + /// parse the clash's config.yaml + /// get some information + pub fn from(config: &Mapping) -> ClashInfoN { + 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, + }; + + ClashInfoN { + status: format!("{status}"), + port, + server, + secret, + } + } +} diff --git a/src-tauri/src/config/mod.rs b/src-tauri/src/config/mod.rs index 7020020..9aa880f 100644 --- a/src-tauri/src/config/mod.rs +++ b/src-tauri/src/config/mod.rs @@ -1,69 +1,9 @@ -mod field; -mod merge; -mod script; -mod tun; +mod clash; +mod prfitem; +mod profiles; +mod verge; -pub(self) use self::field::*; -use self::merge::*; -use self::script::*; -use self::tun::*; -use crate::data::ChainItem; -use crate::data::ChainType; -use serde_yaml::Mapping; -use std::collections::HashMap; -use std::collections::HashSet; - -type ResultLog = Vec<(String, String)>; - -pub fn enhance_config( - clash_config: Mapping, - profile_config: Mapping, - chain: Vec, - valid: Vec, - tun_mode: bool, -) -> (Mapping, Vec, HashMap) { - let mut config = profile_config; - let mut result_map = HashMap::new(); - let mut exists_keys = use_keys(&config); - - let valid = use_valid_fields(valid); - - chain.into_iter().for_each(|item| match item.data { - ChainType::Merge(merge) => { - exists_keys.extend(use_keys(&merge)); - config = use_merge(merge, config.to_owned()); - config = use_filter(config.to_owned(), &valid); - } - ChainType::Script(script) => { - let mut logs = vec![]; - - match use_script(script, config.to_owned()) { - Ok((res_config, res_logs)) => { - exists_keys.extend(use_keys(&res_config)); - config = use_filter(res_config, &valid); - logs.extend(res_logs); - } - Err(err) => logs.push(("exception".into(), err.to_string())), - } - - result_map.insert(item.uid, logs); - } - }); - - config = use_filter(config, &valid); - - for (key, value) in clash_config.into_iter() { - config.insert(key, value); - } - - let clash_fields = use_clash_fields(); - config = use_filter(config, &clash_fields); - config = use_tun(config, tun_mode); - config = use_sort(config); - - let mut exists_set = HashSet::new(); - exists_set.extend(exists_keys.into_iter().filter(|s| clash_fields.contains(s))); - exists_keys = exists_set.into_iter().collect(); - - (config, exists_keys, result_map) -} +pub use self::clash::*; +pub use self::prfitem::*; +pub use self::profiles::*; +pub use self::verge::*; diff --git a/src-tauri/src/config/prfitem.rs b/src-tauri/src/config/prfitem.rs new file mode 100644 index 0000000..62dc9b6 --- /dev/null +++ b/src-tauri/src/config/prfitem.rs @@ -0,0 +1,406 @@ +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, + + /// profile item type + /// enum value: remote | local | script | merge + #[serde(rename = "type")] + pub itype: Option, + + /// profile name + pub name: Option, + + /// profile file + pub file: Option, + + /// profile description + #[serde(skip_serializing_if = "Option::is_none")] + pub desc: Option, + + /// source url + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option, + + /// selected information + #[serde(skip_serializing_if = "Option::is_none")] + pub selected: Option>, + + /// subscription user info + #[serde(skip_serializing_if = "Option::is_none")] + pub extra: Option, + + /// updated time + pub updated: Option, + + /// some options of the item + #[serde(skip_serializing_if = "Option::is_none")] + pub option: Option, + + /// the file data + #[serde(skip)] + pub file_data: Option, +} + +#[derive(Default, Debug, Clone, Deserialize, Serialize)] +pub struct PrfSelected { + pub name: Option, + pub now: Option, +} + +#[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, + + /// for `remote` profile + /// use system proxy + #[serde(skip_serializing_if = "Option::is_none")] + pub with_proxy: Option, + + /// for `remote` profile + /// use self proxy + #[serde(skip_serializing_if = "Option::is_none")] + pub self_proxy: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub update_interval: Option, +} + +impl PrfOption { + pub fn merge(one: Option, other: Option) -> Option { + 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) -> Result { + 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) -> Result { + 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, + desc: Option, + option: Option, + ) -> Result { + 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 clash = super::ClashN::global(); + let port = clash.info.lock().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::(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::() { + 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::(&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 { + 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 quick.js + pub fn from_script(name: String, desc: String) -> Result { + 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 { + 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 { + 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), +} diff --git a/src-tauri/src/config/profiles.rs b/src-tauri/src/config/profiles.rs new file mode 100644 index 0000000..fac9960 --- /dev/null +++ b/src-tauri/src/config/profiles.rs @@ -0,0 +1,379 @@ +use super::{prfitem::PrfItem, ChainItem, PrfOption}; +use crate::{ + core::CoreManager, + 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}; + +pub struct ProfilesN { + pub config: Arc>, +} + +impl ProfilesN { + pub fn global() -> &'static ProfilesN { + static PROFILES: OnceCell = 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) -> Result<()> { + let (url, opt) = { + let profiles = self.config.lock(); + let item = profiles.get_item(&uid)?; + + if let Some(typ) = item.itype.as_ref() { + // maybe only valid for `local` profile + if *typ != "remote" { + // reactivate the config + if Some(uid) == profiles.get_current() { + drop(profiles); + tauri::async_runtime::block_on(async { + CoreManager::global().activate_config().await + })?; + } + return Ok(()); + } + } + if item.url.is_none() { + bail!("failed to get the profile item url"); + } + (item.url.clone().unwrap(), item.option.clone()) + }; + + 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)?; + + // reactivate the profile + if Some(uid) == profiles.get_current() { + drop(profiles); + tauri::async_runtime::block_on(async { + 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, + + /// same as PrfConfig.chain + chain: Option>, + + /// record valid fields for clash + valid: Option>, + + /// profile list + items: Option>, +} + +macro_rules! patch { + ($lv: expr, $rv: expr, $key: tt) => { + if ($rv.$key).is_some() { + $lv.$key = $rv.$key; + } + }; +} + +impl IProfiles { + /// read the config from the file + pub fn read_file() -> Self { + let mut profiles = config::read_yaml::(dirs::profiles_path()); + if profiles.items.is_none() { + profiles.items = Some(vec![]); + } + // compatible 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 { + 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>) -> Result<()> { + self.chain = chain; + self.save_file() + } + + /// just change the `field` + pub fn put_valid(&mut self, valid: Option>) -> Result<()> { + self.valid = valid; + self.save_file() + } + + /// get items ref + pub fn get_items(&self) -> Option<&Vec> { + 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 { + 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 { + 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 { + 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::>(), + 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, + pub valid: Vec, +} + +#[derive(Default, Debug, Clone, Deserialize, Serialize)] +pub struct RuntimeResult { + pub config: Option, + pub config_yaml: Option, + // 记录在配置中(包括merge和script生成的)出现过的keys + // 这些keys不一定都生效 + pub exists_keys: Vec, + pub chain_logs: HashMap>, +} diff --git a/src-tauri/src/config/verge.rs b/src-tauri/src/config/verge.rs new file mode 100644 index 0000000..97b5f10 --- /dev/null +++ b/src-tauri/src/config/verge.rs @@ -0,0 +1,166 @@ +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; + +pub struct VergeN { + pub config: Arc>, +} + +impl VergeN { + pub fn global() -> &'static VergeN { + static DATA: OnceCell = OnceCell::new(); + + DATA.get_or_init(|| { + let config = config::read_yaml::(dirs::verge_path()); + VergeN { + config: Arc::new(Mutex::new(config)), + } + }) + } + + /// Save IVerge App Config + pub fn save_file(&self) -> Result<()> { + let config = self.config.lock(); + + config::save_yaml( + dirs::verge_path(), + &*config, + Some("# The Config for Clash IVerge App\n\n"), + ) + } + + /// patch verge config + /// only save to file + pub fn patch_config(&self, patch: IVerge) -> Result<()> { + let mut config = self.config.lock(); + + macro_rules! patch { + ($key: tt) => { + if patch.$key.is_some() { + config.$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() + } + + /// 在初始化前尝试拿到单例端口的值 + pub fn get_singleton_port() -> u16 { + let config = config::read_yaml::(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)] +pub struct IVerge { + /// app listening port + /// for app singleton + pub app_singleton_port: Option, + + // i18n + pub language: Option, + + /// `light` or `dark` or `system` + pub theme_mode: Option, + + /// enable blur mode + /// maybe be able to set the alpha + pub theme_blur: Option, + + /// enable traffic graph default is true + pub traffic_graph: Option, + + /// clash tun mode + pub enable_tun_mode: Option, + + /// windows service mode + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_service_mode: Option, + + /// can the app auto startup + pub enable_auto_launch: Option, + + /// not show the window on launch + pub enable_silent_start: Option, + + /// set system proxy + pub enable_system_proxy: Option, + + /// enable proxy guard + pub enable_proxy_guard: Option, + + /// set system proxy bypass + pub system_proxy_bypass: Option, + + /// proxy guard duration + pub proxy_guard_duration: Option, + + /// theme setting + pub theme_setting: Option, + + /// web ui list + pub web_ui_list: Option>, + + /// clash core path + #[serde(skip_serializing_if = "Option::is_none")] + pub clash_core: Option, + + /// hotkey map + /// format: {func},{key} + pub hotkeys: Option>, + + /// 切换代理时自动关闭连接 + pub auto_close_connection: Option, + + /// 默认的延迟测试连接 + pub default_latency_test: Option, +} + +#[derive(Default, Debug, Clone, Deserialize, Serialize)] +pub struct IVergeTheme { + pub primary_color: Option, + pub secondary_color: Option, + pub primary_text: Option, + pub secondary_text: Option, + + pub info_color: Option, + pub error_color: Option, + pub warning_color: Option, + pub success_color: Option, + + pub font_family: Option, + pub css_injection: Option, +} diff --git a/src-tauri/src/core/clash_api.rs b/src-tauri/src/core/clash_api.rs new file mode 100644 index 0000000..2504280 --- /dev/null +++ b/src-tauri/src/core/clash_api.rs @@ -0,0 +1,66 @@ +use crate::{config, utils::dirs}; +use anyhow::{bail, Result}; +use reqwest::header::HeaderMap; +use serde_yaml::Mapping; +use std::collections::HashMap; + +/// PUT /configs +pub async fn put_configs() -> Result<()> { + let (url, headers) = clash_client_info()?; + let url = format!("{url}/configs"); + + let runtime_yaml = dirs::clash_runtime_yaml(); + let runtime_yaml = dirs::path_to_str(&runtime_yaml)?; + + let mut data = HashMap::new(); + data.insert("path", runtime_yaml); + + let client = reqwest::ClientBuilder::new().no_proxy().build()?; + let builder = client.put(&url).headers(headers).json(&data); + let response = builder.send().await?; + + match response.status().as_u16() { + 204 => Ok(()), + status @ _ => { + bail!("failed to put configs with status \"{status}\"") + } + } +} + +/// PATCH /configs +pub async fn patch_configs(config: &Mapping) -> Result<()> { + let (url, headers) = clash_client_info()?; + let url = format!("{url}/configs"); + + let client = reqwest::ClientBuilder::new().no_proxy().build()?; + let builder = client.patch(&url).headers(headers.clone()).json(config); + builder.send().await?; + Ok(()) +} + +/// 根据clash info获取clash服务地址和请求头 +fn clash_client_info() -> Result<(String, HeaderMap)> { + let info = { config::ClashN::global().info.lock().clone() }; + + if info.server.is_none() { + let status = &info.status; + if info.port.is_none() { + bail!("failed to parse config.yaml file with status {status}"); + } else { + bail!("failed to parse the server with status {status}"); + } + } + + let server = info.server.unwrap(); + let server = format!("http://{server}"); + + let mut headers = HeaderMap::new(); + headers.insert("Content-Type", "application/json".parse()?); + + if let Some(secret) = info.secret.as_ref() { + let secret = format!("Bearer {}", secret.clone()).parse()?; + headers.insert("Authorization", secret); + } + + Ok((server, headers)) +} diff --git a/src-tauri/src/core/core.rs b/src-tauri/src/core/core.rs new file mode 100644 index 0000000..92296e7 --- /dev/null +++ b/src-tauri/src/core/core.rs @@ -0,0 +1,262 @@ +use super::{clash_api, logger::Logger}; +use crate::{ + config::*, + enhance, + utils::{self, dirs}, +}; +use anyhow::{bail, Context, Result}; +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use std::{fs, io::Write, sync::Arc, time::Duration}; +use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt}; +use tauri::api::process::{Command, CommandChild, CommandEvent}; +use tokio::time::sleep; + +#[derive(Debug)] +pub struct CoreManager { + clash_core: Arc>, + + sidecar: Arc>>, + + #[allow(unused)] + use_service_mode: Arc>, + + pub runtime_config: Arc>, +} + +impl CoreManager { + pub fn global() -> &'static CoreManager { + static CORE_MANAGER: OnceCell = OnceCell::new(); + + CORE_MANAGER.get_or_init(|| CoreManager { + clash_core: Arc::new(Mutex::new("clash".into())), + sidecar: Arc::new(Mutex::new(None)), + runtime_config: Arc::new(Mutex::new(RuntimeResult::default())), + use_service_mode: Arc::new(Mutex::new(false)), + }) + } + + pub fn init(&self) -> Result<()> { + // kill old clash process + if let Ok(pid) = fs::read(dirs::clash_pid_path()) { + if let Ok(pid) = String::from_utf8_lossy(&pid).parse() { + let mut system = System::new(); + system.refresh_all(); + system.process(Pid::from_u32(pid)).map(|proc| { + if proc.name().contains("clash") { + proc.kill(); + } + }); + } + } + + // 使用配置的核心 + let verge = VergeN::global().config.lock(); + if let Some(verge_core) = verge.clash_core.as_ref() { + if verge_core == "clash" || verge_core == "clash-meta" { + let mut clash_core = self.clash_core.lock(); + *clash_core = verge_core.clone(); + } + } + + // 启动clash + self.run_core()?; + + // 更新配置 + tauri::async_runtime::spawn(async { + sleep(Duration::from_millis(100)).await; + crate::log_err!(Self::global().activate_config().await); + }); + + Ok(()) + } + + /// 检查配置是否正确 + pub fn check_config(&self) -> Result<()> { + let config_path = dirs::clash_runtime_yaml(); + let config_path = dirs::path_to_str(&config_path)?; + + let clash_core = { self.clash_core.lock().clone() }; + + let output = Command::new_sidecar(clash_core)? + .args(["-t", config_path]) + .output()?; + + if !output.status.success() { + Logger::global().set_log(output.stderr.clone()); + bail!("{}", output.stderr); // 过滤掉终端颜色值 + } + + Ok(()) + } + + /// 启动核心 + pub fn run_core(&self) -> Result<()> { + // 先纠正重要的配置字段 + self.correct_config()?; + + let mut sidecar = self.sidecar.lock(); + + if let Some(child) = sidecar.take() { + let _ = child.kill(); + } + + let app_dir = dirs::app_home_dir(); + let app_dir = dirs::path_to_str(&app_dir)?; + + let clash_core = { self.clash_core.lock().clone() }; + + // fix #212 + let args = match clash_core.as_str() { + "clash-meta" => vec!["-m", "-d", app_dir], + _ => vec!["-d", app_dir], + }; + + let cmd = Command::new_sidecar(clash_core)?; + let (mut rx, cmd_child) = cmd.args(args).spawn()?; + + // 将pid写入文件中 + crate::log_err!({ + let pid = cmd_child.pid(); + let path = dirs::clash_pid_path(); + fs::File::create(path) + .context("failed to create the pid file")? + .write(format!("{pid}").as_bytes()) + .context("failed to write pid to the file")?; + >::Ok(()) + }); + + *sidecar = Some(cmd_child); + + tauri::async_runtime::spawn(async move { + while let Some(event) = rx.recv().await { + match event { + CommandEvent::Stdout(line) => { + let can_short = line.starts_with("time=") && line.len() > 33; + let stdout = if can_short { &line[33..] } else { &line }; + log::info!(target: "app" ,"[clash]: {}", stdout); + Logger::global().set_log(line); + } + CommandEvent::Stderr(err) => { + log::error!(target: "app" ,"[clash error]: {err}"); + Logger::global().set_log(err); + } + CommandEvent::Error(err) => { + log::error!(target: "app" ,"[clash error]: {err}"); + Logger::global().set_log(err); + } + CommandEvent::Terminated(_) => { + log::info!(target: "app" ,"clash core Terminated"); + break; + } + _ => {} + } + } + }); + + Ok(()) + } + + /// 停止核心运行 + pub fn stop_core(&self) -> Result<()> { + let mut sidecar = self.sidecar.lock(); + if let Some(child) = sidecar.take() { + let _ = child.kill(); + } + Ok(()) + } + + /// 切换核心 + pub async fn change_core(&self, clash_core: Option) -> Result<()> { + let clash_core = clash_core.ok_or(anyhow::anyhow!("clash core is null"))?; + + if &clash_core != "clash" && &clash_core != "clash-meta" { + bail!("invalid clash core name \"{clash_core}\""); + } + + // 清掉旧日志 + Logger::global().clear_log(); + + let mut self_core = self.clash_core.lock(); + let old_core = self_core.clone(); // 保存一下旧值 + *self_core = clash_core.clone(); + drop(self_core); + + match self.run_core() { + Ok(_) => { + // 更新到配置文件 + let mut verge = VergeN::global().config.lock(); + verge.clash_core = Some(clash_core); + drop(verge); + + let _ = VergeN::global().save_file(); + + sleep(Duration::from_millis(100)).await; // 等一会儿再更新配置 + self.activate_config().await?; + Ok(()) + } + Err(err) => { + // 恢复旧的值 + let mut self_core = self.clash_core.lock(); + *self_core = old_core; + Err(err) + } + } + } + + /// 纠正一下配置 + /// 将mixed-port和external-controller都改为配置的内容 + pub fn correct_config(&self) -> Result<()> { + // todo!() + Ok(()) + } + + /// 激活一个配置 + pub async fn activate_config(&self) -> Result<()> { + let clash_config = { ClashN::global().config.lock().clone() }; + + let tun_mode = { VergeN::global().config.lock().enable_tun_mode.clone() }; + let tun_mode = tun_mode.unwrap_or(false); + + let pa = { ProfilesN::global().config.lock().gen_activate()? }; + + let (config, exists_keys, logs) = + enhance::enhance_config(clash_config, pa.current, pa.chain, pa.valid, tun_mode); + + // 保存到文件中 + let runtime_path = dirs::clash_runtime_yaml(); + utils::config::save_yaml(runtime_path, &config, Some("# Clash Verge Runtime Config"))?; + + // 检查配置是否正常 + self.check_config()?; + + // todo 是否需要检查核心是否运行 + + // 发送请求 发送5次 + for i in 0..5 { + match clash_api::put_configs().await { + Ok(_) => break, + Err(err) => { + if i < 4 { + log::error!(target: "app", "{err}"); + } else { + bail!(err); + } + } + } + sleep(Duration::from_millis(250)).await; + } + + // 保存结果 + let mut runtime = self.runtime_config.lock(); + let config_yaml = Some(serde_yaml::to_string(&config).unwrap_or("".into())); + *runtime = RuntimeResult { + config: Some(config), + config_yaml, + exists_keys, + chain_logs: logs, + }; + + Ok(()) + } +} diff --git a/src-tauri/src/core/core_service.rs b/src-tauri/src/core/core_service.rs new file mode 100644 index 0000000..7d52194 --- /dev/null +++ b/src-tauri/src/core/core_service.rs @@ -0,0 +1,217 @@ +#![cfg(target_os = "windows")] + +use crate::utils::{config, dirs}; +use anyhow::Context; +use deelevate::{PrivilegeLevel, Token}; +use runas::Command as RunasCommand; +use serde::{Deserialize, Serialize}; +use std::os::windows::process::CommandExt; +use std::{env::current_exe, process::Command as StdCommand}; + +const SERVICE_NAME: &str = "clash_verge_service"; + +const SERVICE_URL: &str = "http://127.0.0.1:33211"; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct ResponseBody { + pub bin_path: String, + pub config_dir: String, + pub log_file: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct JsonResponse { + pub code: u64, + pub msg: String, + pub data: Option, +} + +impl Service { + /// Install the Clash Verge Service + /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 + pub async fn install_service() -> Result<()> { + let binary_path = dirs::service_path(); + let install_path = binary_path.with_file_name("install-service.exe"); + + if !install_path.exists() { + bail!("installer exe not found"); + } + + let token = Token::with_current_process()?; + let level = token.privilege_level()?; + + let status = match level { + PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).status()?, + _ => StdCommand::new(install_path) + .creation_flags(0x08000000) + .status()?, + }; + + if !status.success() { + bail!( + "failed to install service with status {}", + status.code().unwrap() + ); + } + + Ok(()) + } + + /// Uninstall the Clash Verge Service + /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 + pub async fn uninstall_service() -> Result<()> { + let binary_path = dirs::service_path(); + let uninstall_path = binary_path.with_file_name("uninstall-service.exe"); + + if !uninstall_path.exists() { + bail!("uninstaller exe not found"); + } + + let token = Token::with_current_process()?; + let level = token.privilege_level()?; + + let status = match level { + PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).status()?, + _ => StdCommand::new(uninstall_path) + .creation_flags(0x08000000) + .status()?, + }; + + if !status.success() { + bail!( + "failed to uninstall service with status {}", + status.code().unwrap() + ); + } + + Ok(()) + } + + /// [deprecated] + /// start service + /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 + pub async fn start_service() -> Result<()> { + let token = Token::with_current_process()?; + let level = token.privilege_level()?; + + let args = ["start", SERVICE_NAME]; + + let status = match level { + PrivilegeLevel::NotPrivileged => RunasCommand::new("sc").args(&args).status()?, + _ => StdCommand::new("sc").args(&args).status()?, + }; + + match status.success() { + true => Ok(()), + false => bail!( + "failed to start service with status {}", + status.code().unwrap() + ), + } + } + + /// stop service + pub async fn stop_service() -> Result<()> { + let url = format!("{SERVICE_URL}/stop_service"); + let res = reqwest::ClientBuilder::new() + .no_proxy() + .build()? + .post(url) + .send() + .await? + .json::() + .await + .context("failed to connect to the Clash Verge Service")?; + + if res.code != 0 { + bail!(res.msg); + } + + Ok(()) + } + + /// check the windows service status + pub async fn check_service() -> Result { + let url = format!("{SERVICE_URL}/get_clash"); + let response = reqwest::ClientBuilder::new() + .no_proxy() + .build()? + .get(url) + .send() + .await? + .json::() + .await + .context("failed to connect to the Clash Verge Service")?; + + Ok(response) + } + + /// start the clash by service + pub(super) async fn start_clash_by_service() -> Result<()> { + let status = Self::check_service().await?; + + if status.code == 0 { + Self::stop_clash_by_service().await?; + sleep(Duration::from_secs(1)).await; + } + + let clash_core = { + let global = Data::global(); + let verge = global.verge.lock(); + verge.clash_core.clone().unwrap_or("clash".into()) + }; + + let clash_bin = format!("{clash_core}.exe"); + let bin_path = current_exe().unwrap().with_file_name(clash_bin); + let bin_path = bin_path.as_os_str().to_str().unwrap(); + + let config_dir = dirs::app_home_dir(); + let config_dir = config_dir.as_os_str().to_str().unwrap(); + + let log_path = dirs::service_log_file(); + let log_path = log_path.as_os_str().to_str().unwrap(); + + let mut map = HashMap::new(); + map.insert("bin_path", bin_path); + map.insert("config_dir", config_dir); + map.insert("log_file", log_path); + + let url = format!("{SERVICE_URL}/start_clash"); + let res = reqwest::ClientBuilder::new() + .no_proxy() + .build()? + .post(url) + .json(&map) + .send() + .await? + .json::() + .await + .context("failed to connect to the Clash Verge Service")?; + + if res.code != 0 { + bail!(res.msg); + } + + Ok(()) + } + + /// stop the clash by service + pub(super) async fn stop_clash_by_service() -> Result<()> { + let url = format!("{SERVICE_URL}/stop_clash"); + let res = reqwest::ClientBuilder::new() + .no_proxy() + .build()? + .post(url) + .send() + .await? + .json::() + .await + .context("failed to connect to the Clash Verge Service")?; + + if res.code != 0 { + bail!(res.msg); + } + + Ok(()) + } +} diff --git a/src-tauri/src/core/handle.rs b/src-tauri/src/core/handle.rs index dd04a48..7026e34 100644 --- a/src-tauri/src/core/handle.rs +++ b/src-tauri/src/core/handle.rs @@ -1,65 +1,77 @@ use super::tray::Tray; use crate::log_if_err; use anyhow::{bail, Result}; +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use std::sync::Arc; use tauri::{AppHandle, Manager, Window}; #[derive(Debug, Default, Clone)] pub struct Handle { - pub app_handle: Option, + pub app_handle: Arc>>, } impl Handle { - pub fn set_inner(&mut self, app_handle: AppHandle) { - self.app_handle = Some(app_handle); + pub fn global() -> &'static Handle { + static HANDLE: OnceCell = OnceCell::new(); + + HANDLE.get_or_init(|| Handle { + app_handle: Arc::new(Mutex::new(None)), + }) + } + + pub fn init(&self, app_handle: AppHandle) { + *self.app_handle.lock() = Some(app_handle); } pub fn get_window(&self) -> Option { self.app_handle + .lock() .as_ref() .map_or(None, |a| a.get_window("main")) } - pub fn refresh_clash(&self) { - if let Some(window) = self.get_window() { + pub fn refresh_clash() { + if let Some(window) = Self::global().get_window() { log_if_err!(window.emit("verge://refresh-clash-config", "yes")); } } - pub fn refresh_verge(&self) { - if let Some(window) = self.get_window() { + pub fn refresh_verge() { + if let Some(window) = Self::global().get_window() { log_if_err!(window.emit("verge://refresh-verge-config", "yes")); } } #[allow(unused)] - pub fn refresh_profiles(&self) { - if let Some(window) = self.get_window() { + pub fn refresh_profiles() { + if let Some(window) = Self::global().get_window() { log_if_err!(window.emit("verge://refresh-profiles-config", "yes")); } } - pub fn notice_message(&self, status: String, msg: String) { - if let Some(window) = self.get_window() { - log_if_err!(window.emit("verge://notice-message", (status, msg))); + pub fn notice_message, M: Into>(status: S, msg: M) { + if let Some(window) = Self::global().get_window() { + log_if_err!(window.emit("verge://notice-message", (status.into(), msg.into()))); } } - pub fn update_systray(&self) -> Result<()> { - if self.app_handle.is_none() { - bail!("update_systray unhandle error"); + pub fn update_systray() -> Result<()> { + let app_handle = Self::global().app_handle.lock(); + if app_handle.is_none() { + bail!("update_systray unhandled error"); } - let app_handle = self.app_handle.as_ref().unwrap(); - Tray::update_systray(app_handle)?; + Tray::update_systray(app_handle.as_ref().unwrap())?; Ok(()) } /// update the system tray state - pub fn update_systray_part(&self) -> Result<()> { - if self.app_handle.is_none() { - bail!("update_systray unhandle error"); + pub fn update_systray_part() -> Result<()> { + let app_handle = Self::global().app_handle.lock(); + if app_handle.is_none() { + bail!("update_systray unhandled error"); } - let app_handle = self.app_handle.as_ref().unwrap(); - Tray::update_part(app_handle)?; + Tray::update_part(app_handle.as_ref().unwrap())?; Ok(()) } } diff --git a/src-tauri/src/core/hotkey.rs b/src-tauri/src/core/hotkey.rs index 4ce4847..48c4482 100644 --- a/src-tauri/src/core/hotkey.rs +++ b/src-tauri/src/core/hotkey.rs @@ -1,25 +1,30 @@ -use crate::{data::*, feat, log_if_err}; +use crate::{config, feat, log_err}; use anyhow::{bail, Result}; -use std::collections::HashMap; +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use std::{collections::HashMap, sync::Arc}; use tauri::{AppHandle, GlobalShortcutManager}; pub struct Hotkey { - current: Vec, // 保存当前的热键设置 - manager: Option, + current: Arc>>, // 保存当前的热键设置 + + app_handle: Arc>>, } impl Hotkey { - pub fn new() -> Hotkey { - Hotkey { - current: Vec::new(), - manager: None, - } + pub fn global() -> &'static Hotkey { + static HOTKEY: OnceCell = OnceCell::new(); + + HOTKEY.get_or_init(|| Hotkey { + current: Arc::new(Mutex::new(Vec::new())), + app_handle: Arc::new(Mutex::new(None)), + }) } - pub fn init(&mut self, app_handle: AppHandle) -> Result<()> { - self.manager = Some(app_handle); - let data = Data::global(); - let verge = data.verge.lock(); + pub fn init(&self, app_handle: AppHandle) -> Result<()> { + *self.app_handle.lock() = Some(app_handle); + + let verge = config::VergeN::global().config.lock(); if let Some(hotkeys) = verge.hotkeys.as_ref() { for hotkey in hotkeys.iter() { @@ -28,25 +33,26 @@ impl Hotkey { let key = iter.next(); if func.is_some() && key.is_some() { - log_if_err!(self.register(key.unwrap(), func.unwrap())); + log_err!(self.register(key.unwrap(), func.unwrap())); } else { log::error!(target: "app", "invalid hotkey \"{}\":\"{}\"", key.unwrap_or("None"), func.unwrap_or("None")); } } - self.current = hotkeys.clone(); + *self.current.lock() = hotkeys.clone(); } Ok(()) } fn get_manager(&self) -> Result { - if self.manager.is_none() { + let app_handle = self.app_handle.lock(); + if app_handle.is_none() { bail!("failed to get hotkey manager"); } - Ok(self.manager.as_ref().unwrap().global_shortcut_manager()) + Ok(app_handle.as_ref().unwrap().global_shortcut_manager()) } - fn register(&mut self, hotkey: &str, func: &str) -> Result<()> { + fn register(&self, hotkey: &str, func: &str) -> Result<()> { let mut manager = self.get_manager()?; if manager.is_registered(hotkey)? { @@ -54,16 +60,16 @@ impl Hotkey { } let f = match func.trim() { - "clash_mode_rule" => || feat::change_clash_mode("rule"), - "clash_mode_global" => || feat::change_clash_mode("global"), - "clash_mode_direct" => || feat::change_clash_mode("direct"), - "clash_mode_script" => || feat::change_clash_mode("script"), - "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(), + "clash_mode_rule" => || feat::change_clash_mode("rule".into()), + "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()), _ => bail!("invalid function \"{func}\""), }; @@ -73,14 +79,14 @@ impl Hotkey { Ok(()) } - fn unregister(&mut self, hotkey: &str) -> Result<()> { + fn unregister(&self, hotkey: &str) -> Result<()> { self.get_manager()?.unregister(&hotkey)?; log::info!(target: "app", "unregister hotkey {hotkey}"); Ok(()) } - pub fn update(&mut self, new_hotkeys: Vec) -> Result<()> { - let current = self.current.to_owned(); + pub fn update(&self, new_hotkeys: Vec) -> Result<()> { + let mut current = self.current.lock(); let old_map = Self::get_map_from_vec(¤t); let new_map = Self::get_map_from_vec(&new_hotkeys); @@ -91,10 +97,10 @@ impl Hotkey { }); add.iter().for_each(|(key, func)| { - log_if_err!(self.register(key, func)); + log_err!(self.register(key, func)); }); - self.current = new_hotkeys; + *current = new_hotkeys; Ok(()) } diff --git a/src-tauri/src/core/logger.rs b/src-tauri/src/core/logger.rs new file mode 100644 index 0000000..b426415 --- /dev/null +++ b/src-tauri/src/core/logger.rs @@ -0,0 +1,36 @@ +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use std::{collections::VecDeque, sync::Arc}; + +const LOGS_QUEUE_LEN: usize = 100; + +pub struct Logger { + log_data: Arc>>, +} + +impl Logger { + pub fn global() -> &'static Logger { + static LOGGER: OnceCell = OnceCell::new(); + + LOGGER.get_or_init(|| Logger { + log_data: Arc::new(Mutex::new(VecDeque::with_capacity(LOGS_QUEUE_LEN + 10))), + }) + } + + pub fn get_log(&self) -> VecDeque { + self.log_data.lock().clone() + } + + pub fn set_log(&self, text: String) { + let mut logs = self.log_data.lock(); + if logs.len() > LOGS_QUEUE_LEN { + logs.pop_front(); + } + logs.push_back(text); + } + + pub fn clear_log(&self) { + let mut logs = self.log_data.lock(); + logs.clear(); + } +} diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index c91c609..c0d1d6a 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -1,349 +1,347 @@ -use self::handle::Handle; -use self::hotkey::Hotkey; -use self::sysopt::Sysopt; -use self::timer::Timer; -use crate::config::enhance_config; -use crate::data::*; -use crate::log_if_err; -use anyhow::{bail, Result}; -use once_cell::sync::OnceCell; -use parking_lot::Mutex; -use serde_yaml::{Mapping, Value}; -use std::sync::Arc; +// use self::handle::Handle; +// use self::hotkey::Hotkey; +// use self::sysopt::Sysopt; +// use self::timer::Timer; +// // use crate::data::*; +// // use crate::enhance::enhance_config; +// use crate::log_if_err; +// use anyhow::{bail, Result}; +// use once_cell::sync::OnceCell; +// use parking_lot::Mutex; +// use serde_yaml::{Mapping, Value}; +// use std::sync::Arc; -mod handle; -mod hotkey; -mod service; -mod sysopt; -mod timer; +pub mod clash_api; +mod core; +pub mod core_service; +pub mod handle; +pub mod hotkey; +pub mod logger; +// pub mod service; +pub mod sysopt; +pub mod timer; pub mod tray; -pub use self::service::*; +pub use self::core::*; +// pub use self::service::*; #[derive(Clone)] -pub struct Core { - pub service: Arc>, - pub sysopt: Arc>, - pub timer: Arc>, - pub hotkey: Arc>, - pub runtime: Arc>, - pub handle: Arc>, -} +pub struct Core {} impl Core { - pub fn global() -> &'static Core { - static CORE: OnceCell = OnceCell::new(); + // pub fn global() -> &'static Core { + // static CORE: OnceCell = OnceCell::new(); - CORE.get_or_init(|| Core { - service: Arc::new(Mutex::new(Service::new())), - sysopt: Arc::new(Mutex::new(Sysopt::new())), - timer: Arc::new(Mutex::new(Timer::new())), - hotkey: Arc::new(Mutex::new(Hotkey::new())), - runtime: Arc::new(Mutex::new(RuntimeResult::default())), - handle: Arc::new(Mutex::new(Handle::default())), - }) - } + // CORE.get_or_init(|| Core { + // service: Arc::new(Mutex::new(Service::new())), + // sysopt: Arc::new(Mutex::new(Sysopt::new())), + // timer: Arc::new(Mutex::new(Timer::new())), + // hotkey: Arc::new(Mutex::new(Hotkey::new())), + // runtime: Arc::new(Mutex::new(RuntimeResult::default())), + // handle: Arc::new(Mutex::new(Handle::default())), + // }) + // } - /// initialize the core state - pub fn init(&self, app_handle: tauri::AppHandle) { - // kill old clash process - Service::kill_old_clash(); + // /// initialize the core state + // pub fn init(&self, app_handle: tauri::AppHandle) { + // kill old clash process + // Service::kill_old_clash(); - let mut handle = self.handle.lock(); - handle.set_inner(app_handle.clone()); - drop(handle); + // let mut handle = self.handle.lock(); + // handle.set_inner(app_handle.clone()); + // drop(handle); - let mut service = self.service.lock(); - log_if_err!(service.start()); - drop(service); + // let mut service = self.service.lock(); + // log_if_err!(service.start()); + // drop(service); - log_if_err!(self.activate()); + // log_if_err!(self.activate()); - let mut sysopt = self.sysopt.lock(); - log_if_err!(sysopt.init_launch()); - log_if_err!(sysopt.init_sysproxy()); - drop(sysopt); + // let mut sysopt = self.sysopt.lock(); + // log_if_err!(sysopt.init_launch()); + // log_if_err!(sysopt.init_sysproxy()); + // drop(sysopt); - let handle = self.handle.lock(); - log_if_err!(handle.update_systray_part()); - drop(handle); + // let handle = self.handle.lock(); + // log_if_err!(handle.update_systray_part()); + // drop(handle); - let mut hotkey = self.hotkey.lock(); - log_if_err!(hotkey.init(app_handle)); - drop(hotkey); + // let mut hotkey = self.hotkey.lock(); + // log_if_err!(hotkey.init(app_handle)); + // drop(hotkey); - // timer initialize - let mut timer = self.timer.lock(); - log_if_err!(timer.restore()); - } + // // timer initialize + // let mut timer = self.timer.lock(); + // log_if_err!(timer.restore()); + // } - /// restart the clash sidecar - pub fn restart_clash(&self) -> Result<()> { - let mut service = self.service.lock(); - service.restart()?; - drop(service); - self.activate() - } + // /// restart the clash sidecar + // pub fn restart_clash(&self) -> Result<()> { + // let mut service = self.service.lock(); + // service.restart()?; + // drop(service); + // self.activate() + // } - /// change the clash core - pub fn change_core(&self, clash_core: Option) -> Result<()> { - let clash_core = clash_core.unwrap_or("clash".into()); + // /// change the clash core + // pub fn change_core(&self, clash_core: Option) -> Result<()> { + // let clash_core = clash_core.unwrap_or("clash".into()); - if &clash_core != "clash" && &clash_core != "clash-meta" { - bail!("invalid clash core name \"{clash_core}\""); - } + // if &clash_core != "clash" && &clash_core != "clash-meta" { + // bail!("invalid clash core name \"{clash_core}\""); + // } - let global = Data::global(); - let mut verge = global.verge.lock(); - verge.patch_config(Verge { - clash_core: Some(clash_core.clone()), - ..Verge::default() - })?; - drop(verge); + // let global = Data::global(); + // let mut verge = global.verge.lock(); + // verge.patch_config(Verge { + // clash_core: Some(clash_core.clone()), + // ..Verge::default() + // })?; + // drop(verge); - let mut service = self.service.lock(); - service.clear_logs(); - service.restart()?; - drop(service); + // let mut service = self.service.lock(); + // service.clear_logs(); + // service.restart()?; + // drop(service); - self.activate() - } + // self.activate() + // } - /// Patch Clash - /// handle the clash config changed - pub fn patch_clash(&self, patch: Mapping) -> Result<()> { - let patch_cloned = patch.clone(); - let clash_mode = patch.get("mode"); - let mixed_port = patch.get("mixed-port"); - let external = patch.get("external-controller"); - let secret = patch.get("secret"); + // /// Patch Clash + // /// handle the clash config changed + // pub fn patch_clash(&self, patch: Mapping) -> Result<()> { + // let patch_cloned = patch.clone(); + // let clash_mode = patch.get("mode"); + // let mixed_port = patch.get("mixed-port"); + // let external = patch.get("external-controller"); + // let secret = patch.get("secret"); - let valid_port = { - let global = Data::global(); - let mut clash = global.clash.lock(); - clash.patch_config(patch_cloned)?; - clash.info.port.is_some() - }; + // let valid_port = { + // let global = Data::global(); + // let mut clash = global.clash.lock(); + // clash.patch_config(patch_cloned)?; + // clash.info.port.is_some() + // }; - // todo: port check - if (mixed_port.is_some() && valid_port) || external.is_some() || secret.is_some() { - let mut service = self.service.lock(); - service.restart()?; - drop(service); + // // todo: port check + // if (mixed_port.is_some() && valid_port) || external.is_some() || secret.is_some() { + // let mut service = self.service.lock(); + // service.restart()?; + // drop(service); - self.activate()?; + // self.activate()?; - let mut sysopt = self.sysopt.lock(); - sysopt.init_sysproxy()?; - } + // let mut sysopt = self.sysopt.lock(); + // sysopt.init_sysproxy()?; + // } - if clash_mode.is_some() { - let handle = self.handle.lock(); - handle.update_systray_part()?; - } + // if clash_mode.is_some() { + // let handle = self.handle.lock(); + // handle.update_systray_part()?; + // } - Ok(()) - } + // Ok(()) + // } - /// Patch Verge - pub fn patch_verge(&self, patch: Verge) -> Result<()> { - // save the patch - let global = Data::global(); - let mut verge = global.verge.lock(); - verge.patch_config(patch.clone())?; - drop(verge); + // /// Patch Verge + // pub fn patch_verge(&self, patch: Verge) -> Result<()> { + // // save the patch + // let global = Data::global(); + // let mut verge = global.verge.lock(); + // verge.patch_config(patch.clone())?; + // drop(verge); - 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; + // 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")] - { - let service_mode = patch.enable_service_mode; + // #[cfg(target_os = "windows")] + // { + // let service_mode = patch.enable_service_mode; - // 重启服务 - if service_mode.is_some() { - let mut service = self.service.lock(); - service.restart()?; - drop(service); - } + // // 重启服务 + // if service_mode.is_some() { + // let mut service = self.service.lock(); + // service.restart()?; + // drop(service); + // } - if tun_mode.is_some() && *tun_mode.as_ref().unwrap_or(&false) { - let wintun_dll = crate::utils::dirs::app_home_dir().join("wintun.dll"); - if !wintun_dll.exists() { - bail!("failed to enable TUN for missing `wintun.dll`"); - } - } + // if tun_mode.is_some() && *tun_mode.as_ref().unwrap_or(&false) { + // let wintun_dll = crate::utils::dirs::app_home_dir().join("wintun.dll"); + // if !wintun_dll.exists() { + // bail!("failed to enable TUN for missing `wintun.dll`"); + // } + // } - if service_mode.is_some() || tun_mode.is_some() { - self.activate()?; - } - } + // if service_mode.is_some() || tun_mode.is_some() { + // self.activate()?; + // } + // } - #[cfg(not(target_os = "windows"))] - if tun_mode.is_some() { - self.activate()?; - } + // #[cfg(not(target_os = "windows"))] + // if tun_mode.is_some() { + // self.activate()?; + // } - let mut sysopt = self.sysopt.lock(); + // let mut sysopt = self.sysopt.lock(); - if auto_launch.is_some() { - sysopt.update_launch()?; - } - if system_proxy.is_some() || proxy_bypass.is_some() { - sysopt.update_sysproxy()?; - sysopt.guard_proxy(); - } - if proxy_guard.unwrap_or(false) { - sysopt.guard_proxy(); - } + // if auto_launch.is_some() { + // sysopt.update_launch()?; + // } + // if system_proxy.is_some() || proxy_bypass.is_some() { + // sysopt.update_sysproxy()?; + // sysopt.guard_proxy(); + // } + // if proxy_guard.unwrap_or(false) { + // sysopt.guard_proxy(); + // } - // 更新tray - if language.is_some() { - let handle = self.handle.lock(); - handle.update_systray()?; - } else if system_proxy.is_some() || tun_mode.is_some() { - let handle = self.handle.lock(); - handle.update_systray_part()?; - } + // // 更新tray + // if language.is_some() { + // let handle = self.handle.lock(); + // handle.update_systray()?; + // } else if system_proxy.is_some() || tun_mode.is_some() { + // let handle = self.handle.lock(); + // handle.update_systray_part()?; + // } - if patch.hotkeys.is_some() { - let mut hotkey = self.hotkey.lock(); - hotkey.update(patch.hotkeys.unwrap())?; - } + // if patch.hotkeys.is_some() { + // let mut hotkey = self.hotkey.lock(); + // hotkey.update(patch.hotkeys.unwrap())?; + // } - Ok(()) - } + // Ok(()) + // } - // update rule/global/direct/script mode - pub fn update_mode(&self, mode: &str) -> Result<()> { - // save config to file - let info = { - let global = Data::global(); - let mut clash = global.clash.lock(); - clash.config.insert(Value::from("mode"), Value::from(mode)); - clash.save_config()?; - clash.info.clone() - }; + // /// update rule/global/direct/script mode + // pub fn update_mode(&self, mode: &str) -> Result<()> { + // // save config to file + // let info = { + // let global = Data::global(); + // let mut clash = global.clash.lock(); + // clash.config.insert(Value::from("mode"), Value::from(mode)); + // clash.save_config()?; + // clash.info.clone() + // }; - let mut mapping = Mapping::new(); - mapping.insert(Value::from("mode"), Value::from(mode)); + // let mut mapping = Mapping::new(); + // mapping.insert(Value::from("mode"), Value::from(mode)); - let handle = self.handle.clone(); + // let handle = self.handle.clone(); - tauri::async_runtime::spawn(async move { - log_if_err!(Service::patch_config(info, mapping.to_owned()).await); + // tauri::async_runtime::spawn(async move { + // log_if_err!(Service::patch_config(info, mapping.to_owned()).await); - // update tray - let handle = handle.lock(); - handle.refresh_clash(); - log_if_err!(handle.update_systray_part()); - }); + // // update tray + // let handle = handle.lock(); + // handle.refresh_clash(); + // log_if_err!(handle.update_systray_part()); + // }); - Ok(()) - } + // Ok(()) + // } - /// activate the profile - /// auto activate enhanced profile - /// 触发clash配置更新 - pub fn activate(&self) -> Result<()> { - let global = Data::global(); + // /// activate the profile + // /// auto activate enhanced profile + // /// 触发clash配置更新 + // pub fn activate(&self) -> Result<()> { + // let global = Data::global(); - let verge = global.verge.lock(); - let clash = global.clash.lock(); - let profiles = global.profiles.lock(); + // let verge = global.verge.lock(); + // let clash = global.clash.lock(); + // let profiles = global.profiles.lock(); - let tun_mode = verge.enable_tun_mode.clone().unwrap_or(false); - let profile_activate = profiles.gen_activate()?; + // let tun_mode = verge.enable_tun_mode.clone().unwrap_or(false); + // let profile_activate = profiles.gen_activate()?; - let clash_config = clash.config.clone(); - let clash_info = clash.info.clone(); + // let clash_config = clash.config.clone(); + // let clash_info = clash.info.clone(); - drop(clash); - drop(verge); - drop(profiles); + // drop(clash); + // drop(verge); + // drop(profiles); - let (config, exists_keys, logs) = enhance_config( - clash_config, - profile_activate.current, - profile_activate.chain, - profile_activate.valid, - tun_mode, - ); + // let (config, exists_keys, logs) = enhance_config( + // clash_config, + // profile_activate.current, + // profile_activate.chain, + // profile_activate.valid, + // tun_mode, + // ); - let mut runtime = self.runtime.lock(); - *runtime = RuntimeResult { - config: Some(config.clone()), - config_yaml: Some(serde_yaml::to_string(&config).unwrap_or("".into())), - exists_keys, - chain_logs: logs, - }; - drop(runtime); + // let mut runtime = self.runtime.lock(); + // *runtime = RuntimeResult { + // config: Some(config.clone()), + // config_yaml: Some(serde_yaml::to_string(&config).unwrap_or("".into())), + // exists_keys, + // chain_logs: logs, + // }; + // drop(runtime); - let mut service = self.service.lock(); - service.check_start()?; - drop(service); + // let mut service = self.service.lock(); + // service.check_start()?; + // drop(service); - let handle = self.handle.clone(); - tauri::async_runtime::spawn(async move { - match Service::set_config(clash_info, config).await { - Ok(_) => { - let handle = handle.lock(); - handle.refresh_clash(); - handle.notice_message("set_config::ok".into(), "ok".into()); - } - Err(err) => { - let handle = handle.lock(); - handle.notice_message("set_config::error".into(), format!("{err}")); - log::error!(target: "app", "{err}") - } - } - }); + // let handle = self.handle.clone(); + // tauri::async_runtime::spawn(async move { + // match Service::set_config(clash_info, config).await { + // Ok(_) => { + // let handle = handle.lock(); + // handle.refresh_clash(); + // handle.notice_message("set_config::ok".into(), "ok".into()); + // } + // Err(err) => { + // let handle = handle.lock(); + // handle.notice_message("set_config::error".into(), format!("{err}")); + // log::error!(target: "app", "{err}") + // } + // } + // }); - Ok(()) - } + // Ok(()) + // } - /// Static function - /// update profile item - pub async fn update_profile_item(&self, uid: String, option: Option) -> Result<()> { - let global = Data::global(); + // /// Static function + // /// update profile item + // pub async fn update_profile_item(&self, uid: String, option: Option) -> Result<()> { + // let global = Data::global(); - let (url, opt) = { - let profiles = global.profiles.lock(); - let item = profiles.get_item(&uid)?; + // let (url, opt) = { + // let profiles = global.profiles.lock(); + // let item = profiles.get_item(&uid)?; - if let Some(typ) = item.itype.as_ref() { - // maybe only valid for `local` profile - if *typ != "remote" { - // reactivate the config - if Some(uid) == profiles.get_current() { - drop(profiles); - self.activate()?; - } - return Ok(()); - } - } - if item.url.is_none() { - bail!("failed to get the profile item url"); - } - (item.url.clone().unwrap(), item.option.clone()) - }; + // if let Some(typ) = item.itype.as_ref() { + // // maybe only valid for `local` profile + // if *typ != "remote" { + // // reactivate the config + // if Some(uid) == profiles.get_current() { + // drop(profiles); + // self.activate()?; + // } + // return Ok(()); + // } + // } + // if item.url.is_none() { + // bail!("failed to get the profile item url"); + // } + // (item.url.clone().unwrap(), item.option.clone()) + // }; - let merged_opt = PrfOption::merge(opt, option); - let item = PrfItem::from_url(&url, None, None, merged_opt).await?; + // let merged_opt = PrfOption::merge(opt, option); + // let item = PrfItem::from_url(&url, None, None, merged_opt).await?; - let mut profiles = global.profiles.lock(); - profiles.update_item(uid.clone(), item)?; + // let mut profiles = global.profiles.lock(); + // profiles.update_item(uid.clone(), item)?; - // reactivate the profile - if Some(uid) == profiles.get_current() { - drop(profiles); - self.activate()?; - } + // // reactivate the profile + // if Some(uid) == profiles.get_current() { + // drop(profiles); + // self.activate()?; + // } - Ok(()) - } + // Ok(()) + // } } diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 21fb7ed..36c5fae 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -213,7 +213,7 @@ impl Service { /// update clash config /// using PUT methods pub async fn set_config(info: ClashInfo, config: Mapping) -> Result<()> { - let temp_path = dirs::profiles_temp_path(); + let temp_path = dirs::clash_runtime_yaml(); config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?; let (server, headers) = Self::clash_client_info(info)?; @@ -222,13 +222,13 @@ impl Service { data.insert("path", temp_path.as_os_str().to_str().unwrap()); macro_rules! report_err { - ($i: expr, $e: expr) => { - match $i { - 4 => bail!($e), - _ => log::error!(target: "app", $e), + ($i: expr, $e: expr) => { + match $i { + 4 => bail!($e), + _ => log::error!(target: "app", $e), + } + }; } - }; - } // retry 5 times for i in 0..5 { diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs index 53bf939..7850474 100644 --- a/src-tauri/src/core/sysopt.rs +++ b/src-tauri/src/core/sysopt.rs @@ -1,23 +1,25 @@ -use crate::{data::*, log_if_err}; +use crate::{config, log_err}; use anyhow::{anyhow, bail, Result}; use auto_launch::{AutoLaunch, AutoLaunchBuilder}; +use once_cell::sync::OnceCell; +use parking_lot::Mutex; use std::sync::Arc; use sysproxy::Sysproxy; -use tauri::{async_runtime::Mutex, utils::platform::current_exe}; +use tauri::{async_runtime::Mutex as TokioMutex, utils::platform::current_exe}; pub struct Sysopt { /// current system proxy setting - cur_sysproxy: Option, + cur_sysproxy: Arc>>, /// record the original system proxy /// recover it when exit - old_sysproxy: Option, + old_sysproxy: Arc>>, /// helps to auto launch the app - auto_launch: Option, + auto_launch: Arc>>, /// record whether the guard async is running or not - guard_state: Arc>, + guard_state: Arc>, } #[cfg(target_os = "windows")] @@ -28,44 +30,45 @@ static DEFAULT_BYPASS: &str = "localhost,127.0.0.1/8,::1"; static DEFAULT_BYPASS: &str = "127.0.0.1,localhost,"; impl Sysopt { - pub fn new() -> Sysopt { - Sysopt { - cur_sysproxy: None, - old_sysproxy: None, - auto_launch: None, - guard_state: Arc::new(Mutex::new(false)), - } + pub fn global() -> &'static Sysopt { + static SYSOPT: OnceCell = OnceCell::new(); + + SYSOPT.get_or_init(|| Sysopt { + cur_sysproxy: Arc::new(Mutex::new(None)), + old_sysproxy: Arc::new(Mutex::new(None)), + auto_launch: Arc::new(Mutex::new(None)), + guard_state: Arc::new(TokioMutex::new(false)), + }) } /// init the sysproxy - pub fn init_sysproxy(&mut self) -> Result<()> { - let data = Data::global(); - let clash = data.clash.lock(); - let port = clash.info.port.clone(); + pub fn init_sysproxy(&self) -> Result<()> { + let port = { config::ClashN::global().info.lock().port.clone() }; if port.is_none() { bail!("clash port is none"); } - let verge = data.verge.lock(); + let port = port.unwrap().parse::()?; + let verge = config::VergeN::global().config.lock(); let enable = verge.enable_system_proxy.clone().unwrap_or(false); let bypass = verge.system_proxy_bypass.clone(); let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into()); - let port = port.unwrap().parse::()?; - let host = String::from("127.0.0.1"); - - self.cur_sysproxy = Some(Sysproxy { + let current = Sysproxy { enable, - host, + host: String::from("127.0.0.1"), port, bypass, - }); + }; if enable { - self.old_sysproxy = Sysproxy::get_system_proxy().map_or(None, |p| Some(p)); - self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?; + let old = Sysproxy::get_system_proxy().map_or(None, |p| Some(p)); + current.set_system_proxy()?; + + *self.old_sysproxy.lock() = old; + *self.cur_sysproxy.lock() = Some(current); } // run the system proxy guard @@ -74,37 +77,44 @@ impl Sysopt { } /// update the system proxy - pub fn update_sysproxy(&mut self) -> Result<()> { - if self.cur_sysproxy.is_none() || self.old_sysproxy.is_none() { + pub fn update_sysproxy(&self) -> Result<()> { + let mut cur_sysproxy = self.cur_sysproxy.lock(); + let old_sysproxy = self.old_sysproxy.lock(); + + if cur_sysproxy.is_none() || old_sysproxy.is_none() { + drop(cur_sysproxy); + drop(old_sysproxy); return self.init_sysproxy(); } - let data = Data::global(); - let verge = data.verge.lock(); + let verge = config::VergeN::global().config.lock(); let enable = verge.enable_system_proxy.clone().unwrap_or(false); let bypass = verge.system_proxy_bypass.clone(); let bypass = bypass.unwrap_or(DEFAULT_BYPASS.into()); - let mut sysproxy = self.cur_sysproxy.take().unwrap(); + let mut sysproxy = cur_sysproxy.take().unwrap(); sysproxy.enable = enable; sysproxy.bypass = bypass; - self.cur_sysproxy = Some(sysproxy); - self.cur_sysproxy.as_ref().unwrap().set_system_proxy()?; + sysproxy.set_system_proxy()?; + *cur_sysproxy = Some(sysproxy); Ok(()) } /// reset the sysproxy - pub fn reset_sysproxy(&mut self) -> Result<()> { - let cur = self.cur_sysproxy.take(); + pub fn reset_sysproxy(&self) -> Result<()> { + let mut cur_sysproxy = self.cur_sysproxy.lock(); + let mut old_sysproxy = self.old_sysproxy.lock(); - if let Some(mut old) = self.old_sysproxy.take() { + let cur_sysproxy = cur_sysproxy.take(); + + if let Some(mut old) = old_sysproxy.take() { // 如果原代理和当前代理 端口一致,就disable关闭,否则就恢复原代理设置 // 当前没有设置代理的时候,不确定旧设置是否和当前一致,全关了 - let port_same = cur.map_or(true, |cur| old.port == cur.port); + let port_same = cur_sysproxy.map_or(true, |cur| old.port == cur.port); if old.enable && port_same { old.enable = false; @@ -114,7 +124,7 @@ impl Sysopt { } old.set_system_proxy()?; - } else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur { + } else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur_sysproxy { // 没有原代理,就按现在的代理设置disable即可 log::info!(target: "app", "reset proxy by disabling the current proxy"); cur.enable = false; @@ -127,9 +137,8 @@ impl Sysopt { } /// init the auto launch - pub fn init_launch(&mut self) -> Result<()> { - let data = Data::global(); - let verge = data.verge.lock(); + pub fn init_launch(&self) -> Result<()> { + let verge = config::VergeN::global().config.lock(); let enable = verge.enable_auto_launch.clone().unwrap_or(false); let app_exe = current_exe()?; @@ -167,51 +176,41 @@ impl Sysopt { .set_app_path(&app_path) .build()?; - self.auto_launch = Some(auto); - // 避免在开发时将自启动关了 #[cfg(feature = "verge-dev")] if !enable { return Ok(()); } - let auto = self.auto_launch.as_ref().unwrap(); - // macos每次启动都更新登录项,避免重复设置登录项 #[cfg(target_os = "macos")] - { - let _ = auto.disable(); - if enable { - auto.enable()?; - } - } + let _ = auto.disable(); - #[cfg(not(target_os = "macos"))] - { - match enable { - true => auto.enable()?, - false => auto.disable()?, - }; + if enable { + auto.enable()?; } + *self.auto_launch.lock() = Some(auto); Ok(()) } /// update the startup - pub fn update_launch(&mut self) -> Result<()> { - if self.auto_launch.is_none() { + pub fn update_launch(&self) -> Result<()> { + let auto_launch = self.auto_launch.lock(); + + if auto_launch.is_none() { + drop(auto_launch); return self.init_launch(); } - let data = Data::global(); - let verge = data.verge.lock(); + let verge = config::VergeN::global().config.lock(); let enable = verge.enable_auto_launch.clone().unwrap_or(false); - let auto_launch = self.auto_launch.as_ref().unwrap(); + let auto_launch = auto_launch.as_ref().unwrap(); match enable { true => auto_launch.enable()?, - false => crate::log_if_err!(auto_launch.disable()), // 忽略关闭的错误 + false => log_err!(auto_launch.disable()), // 忽略关闭的错误 }; Ok(()) @@ -239,8 +238,7 @@ impl Sysopt { loop { sleep(Duration::from_secs(wait_secs)).await; - let global = Data::global(); - let verge = global.verge.lock(); + let verge = config::VergeN::global().config.lock(); let enable = verge.enable_system_proxy.clone().unwrap_or(false); let guard = verge.enable_proxy_guard.clone().unwrap_or(false); @@ -256,14 +254,10 @@ impl Sysopt { // update duration wait_secs = guard_duration; - let clash = global.clash.lock(); - let port = clash.info.port.clone(); - let port = port.unwrap_or("".into()).parse::(); - drop(clash); - log::debug!(target: "app", "try to guard the system proxy"); - match port { + let port = { config::ClashN::global().info.lock().port.clone() }; + match port.unwrap_or("".into()).parse::() { Ok(port) => { let sysproxy = Sysproxy { enable: true, @@ -272,14 +266,17 @@ impl Sysopt { bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()), }; - log_if_err!(sysproxy.set_system_proxy()); + log_err!(sysproxy.set_system_proxy()); + } + Err(_) => { + log::error!(target: "app", "failed to parse clash port in guard proxy") } - Err(_) => log::error!(target: "app", "failed to parse clash port"), } } let mut state = guard_state.lock().await; *state = false; + drop(state); }); } } diff --git a/src-tauri/src/core/timer.rs b/src-tauri/src/core/timer.rs index 4d9ccba..8859dcc 100644 --- a/src-tauri/src/core/timer.rs +++ b/src-tauri/src/core/timer.rs @@ -1,50 +1,96 @@ -use super::Core; -use crate::utils::help::get_now; -use crate::{data::Data, log_if_err}; +use crate::config::{self, ProfilesN}; use anyhow::{Context, Result}; use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder}; +use once_cell::sync::OnceCell; +use parking_lot::Mutex; use std::collections::HashMap; +use std::sync::Arc; type TaskID = u64; pub struct Timer { /// cron manager - delay_timer: DelayTimer, + delay_timer: Arc>, /// save the current state - timer_map: HashMap, + timer_map: Arc>>, /// increment id - timer_count: TaskID, + timer_count: Arc>, } impl Timer { - pub fn new() -> Self { - Timer { - delay_timer: DelayTimerBuilder::default().build(), - timer_map: HashMap::new(), - timer_count: 1, - } + pub fn global() -> &'static Timer { + static TIMER: OnceCell = OnceCell::new(); + + TIMER.get_or_init(|| Timer { + delay_timer: Arc::new(Mutex::new(DelayTimerBuilder::default().build())), + timer_map: Arc::new(Mutex::new(HashMap::new())), + timer_count: Arc::new(Mutex::new(1)), + }) + } + + /// restore timer + pub fn init(&self) -> Result<()> { + self.refresh()?; + + 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| { + items + .iter() + // .filter_map(|item| { + // item.uid.is_some() && item.updated.is_some() && item.option.is_some() + // }) + .filter_map(|item| { + // mins to seconds + let interval = ((item.option.as_ref()?.update_interval?) as i64) * 60; + let updated = item.updated? as i64; + + if interval > 0 && cur_timestamp - updated >= interval { + Some(item) + } else { + None + } + }) + .for_each(|item| { + if let Some(uid) = item.uid.as_ref() { + if let Some((task_id, _)) = timer_map.get(uid) { + crate::log_err!(delay_timer.advance_task(*task_id)); + } + } + }) + }); + + Ok(()) } /// Correctly update all cron tasks - pub fn refresh(&mut self) -> Result<()> { + pub fn refresh(&self) -> Result<()> { let diff_map = self.gen_diff(); + let mut timer_map = self.timer_map.lock(); + let delay_timer = self.delay_timer.lock(); + for (uid, diff) in diff_map.into_iter() { match diff { DiffFlag::Del(tid) => { - let _ = self.timer_map.remove(&uid); - log_if_err!(self.delay_timer.remove_task(tid)); + let _ = timer_map.remove(&uid); + crate::log_err!(delay_timer.remove_task(tid)); } DiffFlag::Add(tid, val) => { - let _ = self.timer_map.insert(uid.clone(), (tid, val)); - log_if_err!(self.add_task(uid, tid, val)); + let _ = timer_map.insert(uid.clone(), (tid, val)); + crate::log_err!(self.add_task(uid, tid, val)); } DiffFlag::Mod(tid, val) => { - let _ = self.timer_map.insert(uid.clone(), (tid, val)); - log_if_err!(self.delay_timer.remove_task(tid)); - log_if_err!(self.add_task(uid, tid, val)); + let _ = timer_map.insert(uid.clone(), (tid, val)); + crate::log_err!(delay_timer.remove_task(tid)); + crate::log_err!(self.add_task(uid, tid, val)); } } } @@ -52,41 +98,9 @@ impl Timer { Ok(()) } - /// restore timer - pub fn restore(&mut self) -> Result<()> { - self.refresh()?; - - let cur_timestamp = get_now(); // seconds - - let global = Data::global(); - let profiles = global.profiles.lock(); - - profiles - .get_items() - .unwrap_or(&vec![]) - .iter() - .filter(|item| item.uid.is_some() && item.updated.is_some() && item.option.is_some()) - .filter(|item| { - // mins to seconds - let interval = - item.option.as_ref().unwrap().update_interval.unwrap_or(0) as usize * 60; - let updated = item.updated.unwrap(); - return interval > 0 && cur_timestamp - updated >= interval; - }) - .for_each(|item| { - let uid = item.uid.as_ref().unwrap(); - if let Some((task_id, _)) = self.timer_map.get(uid) { - log_if_err!(self.delay_timer.advance_task(*task_id)); - } - }); - - Ok(()) - } - /// generate a uid -> update_interval map fn gen_map(&self) -> HashMap { - let global = Data::global(); - let profiles = global.profiles.lock(); + let profiles = config::ProfilesN::global().config.lock(); let mut new_map = HashMap::new(); @@ -107,11 +121,13 @@ impl Timer { } /// generate the diff map for refresh - fn gen_diff(&mut self) -> HashMap { + fn gen_diff(&self) -> HashMap { let mut diff_map = HashMap::new(); + let timer_map = self.timer_map.lock(); + let new_map = self.gen_map(); - let cur_map = &self.timer_map; + let cur_map = &timer_map; cur_map.iter().for_each(|(uid, (tid, val))| { let new_val = new_map.get(uid).unwrap_or(&0); @@ -123,34 +139,31 @@ impl Timer { } }); - let mut count = self.timer_count; + let mut count = self.timer_count.lock(); new_map.iter().for_each(|(uid, val)| { if cur_map.get(uid).is_none() { - diff_map.insert(uid.clone(), DiffFlag::Add(count, *val)); + diff_map.insert(uid.clone(), DiffFlag::Add(*count, *val)); - count += 1; + *count += 1; } }); - self.timer_count = count; - diff_map } /// add a cron task fn add_task(&self, uid: String, tid: TaskID, minutes: u64) -> Result<()> { - let core = Core::global(); - let task = TaskBuilder::default() .set_task_id(tid) .set_maximum_parallel_runnable_num(1) .set_frequency_repeated_by_minutes(minutes) // .set_frequency_repeated_by_seconds(minutes) // for test - .spawn_async_routine(move || Self::async_task(core.to_owned(), uid.to_owned())) + .spawn_async_routine(move || Self::async_task(uid.to_owned())) .context("failed to create timer task")?; self.delay_timer + .lock() .add_task(task) .context("failed to add timer task")?; @@ -158,9 +171,9 @@ impl Timer { } /// the task runner - async fn async_task(core: Core, uid: String) { + async fn async_task(uid: String) { log::info!(target: "app", "running timer task `{uid}`"); - log_if_err!(core.update_profile_item(uid, None).await); + crate::log_err!(ProfilesN::global().update_item(uid, None).await); } } diff --git a/src-tauri/src/core/tray.rs b/src-tauri/src/core/tray.rs index a65c457..20762cb 100644 --- a/src-tauri/src/core/tray.rs +++ b/src-tauri/src/core/tray.rs @@ -1,5 +1,6 @@ -use crate::{data::Data, feat, utils::resolve}; -use anyhow::{Ok, Result}; +use crate::log_err; +use crate::{config, feat, utils::resolve}; +use anyhow::Result; use tauri::{ api, AppHandle, CustomMenuItem, Manager, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem, SystemTraySubmenu, @@ -9,14 +10,16 @@ pub struct Tray {} impl Tray { pub fn tray_menu(app_handle: &AppHandle) -> SystemTrayMenu { - let data = Data::global(); let zh = { - let verge = data.verge.lock(); + let verge = config::VergeN::global().config.lock(); verge.language == Some("zh".into()) }; let version = app_handle.package_info().version.to_string(); + dbg!(&zh); + dbg!(&version); + if zh { SystemTrayMenu::new() .add_item(CustomMenuItem::new("open_window", "打开面板")) @@ -75,13 +78,14 @@ impl Tray { } pub fn update_part(app_handle: &AppHandle) -> Result<()> { - let global = Data::global(); - let clash = global.clash.lock(); - let mode = clash - .config - .get(&serde_yaml::Value::from("mode")) - .map(|val| val.as_str().unwrap_or("rule")) - .unwrap_or("rule"); + let mode = { + let clash = config::ClashN::global().config.lock(); + clash + .get("mode") + .map(|val| val.as_str().unwrap_or("rule")) + .unwrap_or("rule") + .to_owned() + }; let tray = app_handle.tray_handle(); @@ -90,7 +94,7 @@ impl Tray { let _ = tray.get_item("direct_mode").set_selected(mode == "direct"); let _ = tray.get_item("script_mode").set_selected(mode == "script"); - let verge = global.verge.lock(); + let verge = config::VergeN::global().config.lock(); let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false); let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); @@ -105,12 +109,12 @@ impl Tray { SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() { mode @ ("rule_mode" | "global_mode" | "direct_mode" | "script_mode") => { let mode = &mode[0..mode.len() - 5]; - feat::change_clash_mode(mode); + feat::change_clash_mode(mode.into()); } "open_window" => resolve::create_window(app_handle), - "system_proxy" => feat::toggle_system_proxy(), - "tun_mode" => feat::toggle_tun_mode(), + "system_proxy" => log_err!(feat::toggle_system_proxy()), + "tun_mode" => log_err!(feat::toggle_tun_mode()), "restart_clash" => feat::restart_clash_core(), "restart_app" => api::process::restart(&app_handle.env()), "quit" => { @@ -124,7 +128,9 @@ impl Tray { SystemTrayEvent::LeftClick { .. } => { resolve::create_window(app_handle); } - _ => {} + e @ _ => { + dbg!("trya"); + } } } } diff --git a/src-tauri/src/config/field.rs b/src-tauri/src/enhance/field.rs similarity index 100% rename from src-tauri/src/config/field.rs rename to src-tauri/src/enhance/field.rs diff --git a/src-tauri/src/config/merge.rs b/src-tauri/src/enhance/merge.rs similarity index 100% rename from src-tauri/src/config/merge.rs rename to src-tauri/src/enhance/merge.rs diff --git a/src-tauri/src/enhance/mod.rs b/src-tauri/src/enhance/mod.rs new file mode 100644 index 0000000..0b00035 --- /dev/null +++ b/src-tauri/src/enhance/mod.rs @@ -0,0 +1,68 @@ +mod field; +mod merge; +mod script; +mod tun; + +pub(self) use self::field::*; +use self::merge::*; +use self::script::*; +use self::tun::*; +use crate::config::{ChainItem, ChainType}; +use serde_yaml::Mapping; +use std::collections::HashMap; +use std::collections::HashSet; + +type ResultLog = Vec<(String, String)>; + +pub fn enhance_config( + clash_config: Mapping, + profile_config: Mapping, + chain: Vec, + valid: Vec, + tun_mode: bool, +) -> (Mapping, Vec, HashMap) { + let mut config = profile_config; + let mut result_map = HashMap::new(); + let mut exists_keys = use_keys(&config); + + let valid = use_valid_fields(valid); + + chain.into_iter().for_each(|item| match item.data { + ChainType::Merge(merge) => { + exists_keys.extend(use_keys(&merge)); + config = use_merge(merge, config.to_owned()); + config = use_filter(config.to_owned(), &valid); + } + ChainType::Script(script) => { + let mut logs = vec![]; + + match use_script(script, config.to_owned()) { + Ok((res_config, res_logs)) => { + exists_keys.extend(use_keys(&res_config)); + config = use_filter(res_config, &valid); + logs.extend(res_logs); + } + Err(err) => logs.push(("exception".into(), err.to_string())), + } + + result_map.insert(item.uid, logs); + } + }); + + config = use_filter(config, &valid); + + for (key, value) in clash_config.into_iter() { + config.insert(key, value); + } + + let clash_fields = use_clash_fields(); + config = use_filter(config, &clash_fields); + config = use_tun(config, tun_mode); + config = use_sort(config); + + let mut exists_set = HashSet::new(); + exists_set.extend(exists_keys.into_iter().filter(|s| clash_fields.contains(s))); + exists_keys = exists_set.into_iter().collect(); + + (config, exists_keys, result_map) +} diff --git a/src-tauri/src/config/script.rs b/src-tauri/src/enhance/script.rs similarity index 100% rename from src-tauri/src/config/script.rs rename to src-tauri/src/enhance/script.rs diff --git a/src-tauri/src/config/tun.rs b/src-tauri/src/enhance/tun.rs similarity index 100% rename from src-tauri/src/config/tun.rs rename to src-tauri/src/enhance/tun.rs diff --git a/src-tauri/src/feat.rs b/src-tauri/src/feat.rs index 79fade1..39d6d98 100644 --- a/src-tauri/src/feat.rs +++ b/src-tauri/src/feat.rs @@ -1,102 +1,214 @@ +use crate::config::*; use crate::core::*; -use crate::data::*; -use crate::log_if_err; +use crate::log_err; +use anyhow::Result; +use serde_yaml::{Mapping, Value}; // 重启clash pub fn restart_clash_core() { - let core = Core::global(); - let mut service = core.service.lock(); - log_if_err!(service.restart()); - drop(service); - log_if_err!(core.activate()); + tauri::async_runtime::spawn(async { + CoreManager::global().run_core()?; + log_err!(handle_activate().await); + >::Ok(()) + }); } -// 切换模式 -pub fn change_clash_mode(mode: &str) { - let core = Core::global(); - log_if_err!(core.update_mode(mode)); +// 切换模式 rule/global/direct/script mode +pub fn change_clash_mode(mode: String) { + let mut mapping = Mapping::new(); + mapping.insert(Value::from("mode"), mode.clone().into()); + + tauri::async_runtime::spawn(async move { + match clash_api::patch_configs(&mapping).await { + Ok(_) => { + // 更新配置 + let mut clash = ClashN::global().config.lock(); + clash.insert(Value::from("mode"), mode.into()); + drop(clash); + + if let Ok(_) = ClashN::global().save_config() { + handle::Handle::refresh_clash(); + log_err!(handle::Handle::update_systray_part()); + } + } + Err(err) => { + log::error!(target: "app", "{err}"); + } + } + }); } // 切换系统代理 -pub fn toggle_system_proxy() { - let core = Core::global(); - let data = Data::global(); - - let verge = data.verge.lock(); - let enable = !verge.enable_system_proxy.clone().unwrap_or(false); - drop(verge); - - log_if_err!(core.patch_verge(Verge { - enable_system_proxy: Some(enable), - ..Verge::default() - })); - - let handle = core.handle.lock(); - let _ = handle.refresh_verge(); +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 enable_system_proxy() { - let core = Core::global(); - log_if_err!(core.patch_verge(Verge { +pub fn enable_system_proxy() -> Result<()> { + patch_verge(IVerge { enable_system_proxy: Some(true), - ..Verge::default() - })); - - let handle = core.handle.lock(); - let _ = handle.refresh_verge(); + ..IVerge::default() + })?; + handle::Handle::refresh_verge(); + Ok(()) } // 关闭系统代理 -pub fn disable_system_proxy() { - let core = Core::global(); - log_if_err!(core.patch_verge(Verge { +pub fn disable_system_proxy() -> Result<()> { + patch_verge(IVerge { enable_system_proxy: Some(false), - ..Verge::default() - })); - - let handle = core.handle.lock(); - let _ = handle.refresh_verge(); + ..IVerge::default() + })?; + handle::Handle::refresh_verge(); + Ok(()) } // 切换tun模式 -pub fn toggle_tun_mode() { - let core = Core::global(); - let data = Data::global(); +pub fn toggle_tun_mode() -> Result<()> { + let enable = { + let verge = VergeN::global().config.lock(); + verge.enable_tun_mode.clone().unwrap_or(false) + }; - let verge = data.verge.lock(); - let enable = !verge.enable_tun_mode.clone().unwrap_or(false); - drop(verge); - - log_if_err!(core.patch_verge(Verge { - enable_tun_mode: Some(enable), - ..Verge::default() - })); - - let handle = core.handle.lock(); - let _ = handle.refresh_verge(); + patch_verge(IVerge { + enable_tun_mode: Some(!enable), + ..IVerge::default() + })?; + handle::Handle::refresh_verge(); + Ok(()) } // 打开tun模式 -pub fn enable_tun_mode() { - let core = Core::global(); - log_if_err!(core.patch_verge(Verge { +pub fn enable_tun_mode() -> Result<()> { + patch_verge(IVerge { enable_tun_mode: Some(true), - ..Verge::default() - })); - - let handle = core.handle.lock(); - let _ = handle.refresh_verge(); + ..IVerge::default() + })?; + handle::Handle::refresh_verge(); + Ok(()) } // 关闭tun模式 -pub fn disable_tun_mode() { - let core = Core::global(); - log_if_err!(core.patch_verge(Verge { +pub fn disable_tun_mode() -> Result<()> { + patch_verge(IVerge { enable_tun_mode: Some(false), - ..Verge::default() - })); - - let handle = core.handle.lock(); - let _ = handle.refresh_verge(); + ..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()); + } + } + Err(err) => log::error!(target: "app", "{err}"), + } + }); + Ok(()) +} + +/// 修改verge的配置 +/// 一般都是一个个的修改 +pub fn patch_verge(patch: IVerge) -> Result<()> { + VergeN::global().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")] + {} + + if tun_mode.is_some() { + tauri::async_runtime::spawn(async { + log_err!(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 language.is_some() { + handle::Handle::update_systray()?; + } else if system_proxy.or(tun_mode).is_some() { + handle::Handle::update_systray_part()?; + } + + if patch.hotkeys.is_some() { + hotkey::Hotkey::global().update(patch.hotkeys.unwrap())?; + } + + Ok(()) +} + +/// 激活配置 +pub async fn handle_activate() -> Result<()> { + match CoreManager::global().activate_config().await { + Ok(_) => { + handle::Handle::refresh_clash(); + handle::Handle::notice_message("set_config::ok", "ok"); + Ok(()) + } + Err(err) => { + handle::Handle::notice_message("set_config::error", format!("{err}")); + Err(err) + } + } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 066939b..40010c6 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -6,12 +6,13 @@ mod cmds; mod config; mod core; -mod data; +// mod data; +mod enhance; mod feat; mod utils; use crate::utils::{init, resolve, server}; -use tauri::{api, Manager, SystemTray}; +use tauri::{api, CustomMenuItem, Manager, SystemTray, SystemTrayMenu}; fn main() -> std::io::Result<()> { // 单例检测 @@ -30,8 +31,8 @@ fn main() -> std::io::Result<()> { #[allow(unused_mut)] let mut builder = tauri::Builder::default() - .setup(|app| Ok(resolve::resolve_setup(app))) .system_tray(SystemTray::new()) + .setup(|app| Ok(resolve::resolve_setup(app))) .on_system_tray_event(core::tray::Tray::on_system_tray_event) .invoke_handler(tauri::generate_handler![ // common @@ -39,7 +40,7 @@ fn main() -> std::io::Result<()> { cmds::open_app_dir, cmds::open_logs_dir, cmds::open_web_url, - cmds::kill_sidecar, + // cmds::kill_sidecar, cmds::restart_sidecar, // clash cmds::get_clash_info, @@ -53,12 +54,12 @@ fn main() -> std::io::Result<()> { // verge cmds::get_verge_config, cmds::patch_verge_config, - cmds::update_hotkeys, + // cmds::update_hotkeys, // profile cmds::view_profile, cmds::patch_profile, cmds::create_profile, - cmds::import_profile, + // cmds::import_profile, cmds::update_profile, cmds::delete_profile, cmds::select_profile, @@ -96,22 +97,17 @@ fn main() -> std::io::Result<()> { ); } - #[allow(unused_mut)] - let mut app = builder + let app = builder .build(tauri::generate_context!()) .expect("error while running tauri application"); - #[cfg(target_os = "macos")] - app.set_activation_policy(tauri::ActivationPolicy::Accessory); + // let app_handle = app.app_handle(); + // ctrlc::set_handler(move || { + // resolve::resolve_reset(); + // app_handle.exit(0); + // }) + // .expect("error while exiting."); - let app_handle = app.app_handle(); - ctrlc::set_handler(move || { - resolve::resolve_reset(); - app_handle.exit(0); - }) - .expect("error while exiting."); - - #[allow(unused)] app.run(|app_handle, e| match e { tauri::RunEvent::ExitRequested { api, .. } => { api.prevent_exit(); diff --git a/src-tauri/src/utils/config.rs b/src-tauri/src/utils/config.rs index 17fe14b..efcdf6a 100644 --- a/src-tauri/src/utils/config.rs +++ b/src-tauri/src/utils/config.rs @@ -50,7 +50,7 @@ pub fn save_yaml(path: PathBuf, data: &T, prefix: Option<&str>) -> let data_str = serde_yaml::to_string(data)?; let yaml_str = match prefix { - Some(prefix) => format!("{prefix}{data_str}"), + Some(prefix) => format!("{prefix}\n{data_str}"), None => data_str, }; diff --git a/src-tauri/src/utils/dirs.rs b/src-tauri/src/utils/dirs.rs index b810180..68cb423 100644 --- a/src-tauri/src/utils/dirs.rs +++ b/src-tauri/src/utils/dirs.rs @@ -1,4 +1,3 @@ -use std::env::temp_dir; use std::path::PathBuf; use tauri::{ api::path::{home_dir, resource_dir}, @@ -13,7 +12,7 @@ static APP_DIR: &str = "clash-verge-dev"; static CLASH_CONFIG: &str = "config.yaml"; static VERGE_CONFIG: &str = "verge.yaml"; static PROFILE_YAML: &str = "profiles.yaml"; -static PROFILE_TEMP: &str = "clash-verge-runtime.yaml"; +static CLASH_RUNTIME_YAML: &str = "clash-verge-runtime.yaml"; static mut RESOURCE_DIR: Option = None; @@ -99,12 +98,8 @@ pub fn profiles_path() -> PathBuf { app_home_dir().join(PROFILE_YAML) } -pub fn profiles_temp_path() -> PathBuf { - #[cfg(not(feature = "debug-yml"))] - return temp_dir().join(PROFILE_TEMP); - - #[cfg(feature = "debug-yml")] - return app_home_dir().join(PROFILE_TEMP); +pub fn clash_runtime_yaml() -> PathBuf { + app_home_dir().join(CLASH_RUNTIME_YAML) } pub fn clash_pid_path() -> PathBuf { @@ -136,3 +131,11 @@ pub fn service_log_file() -> PathBuf { log_file } + +pub fn path_to_str(path: &PathBuf) -> anyhow::Result<&str> { + let path_str = path + .as_os_str() + .to_str() + .ok_or(anyhow::anyhow!("failed to get path from {:?}", path))?; + Ok(path_str) +} diff --git a/src-tauri/src/utils/help.rs b/src-tauri/src/utils/help.rs index b8ae558..0c5bb7e 100644 --- a/src-tauri/src/utils/help.rs +++ b/src-tauri/src/utils/help.rs @@ -69,28 +69,50 @@ pub fn open_file(path: PathBuf) -> Result<()> { Ok(()) } +#[macro_export] +macro_rules! error { + ($result: expr) => { + log::error!(target: "app", "{}", $result); + }; +} + #[macro_export] macro_rules! log_if_err { - ($result: expr) => { - if let Err(err) = $result { - log::error!(target: "app", "{err}"); - } - }; + ($result: expr) => { + if let Err(err) = $result { + log::error!(target: "app", "{err}"); + } + }; +} + +#[macro_export] +macro_rules! log_err { + ($result: expr) => { + if let Err(err) = $result { + log::error!(target: "app", "{err}"); + } + }; + + ($result: expr, $err_str: expr) => { + if let Err(_) = $result { + log::error!(target: "app", "{}", $err_str); + } + }; } /// wrap the anyhow error /// transform the error to String #[macro_export] macro_rules! wrap_err { - ($stat: expr) => { - match $stat { - Ok(a) => Ok(a), - Err(err) => { - log::error!(target: "app", "{}", err.to_string()); - Err(format!("{}", err.to_string())) - } - } - }; + ($stat: expr) => { + match $stat { + Ok(a) => Ok(a), + Err(err) => { + log::error!(target: "app", "{}", err.to_string()); + Err(format!("{}", err.to_string())) + } + } + }; } /// return the string literal error diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs index cadb64a..bb36352 100644 --- a/src-tauri/src/utils/init.rs +++ b/src-tauri/src/utils/init.rs @@ -87,19 +87,28 @@ pub fn init_resources(package_info: &PackageInfo) { } // copy the resource file - let mmdb_path = app_dir.join("Country.mmdb"); - let mmdb_tmpl = res_dir.join("Country.mmdb"); - if !mmdb_path.exists() && mmdb_tmpl.exists() { - let _ = fs::copy(mmdb_tmpl, mmdb_path); - } - - // copy the wintun.dll - #[cfg(target_os = "windows")] - { - let wintun_path = app_dir.join("wintun.dll"); - let wintun_tmpl = res_dir.join("wintun.dll"); - if !wintun_path.exists() && wintun_tmpl.exists() { - let _ = fs::copy(wintun_tmpl, wintun_path); + for file in ["Country.mmdb", "geoip.dat", "geosite.dat", "wintun.dll"].iter() { + let src_path = res_dir.join(file); + let target_path = app_dir.join(file); + if src_path.exists() { + let _ = fs::copy(src_path, target_path); } } + + // // copy the resource file + // let mmdb_path = app_dir.join("Country.mmdb"); + // let mmdb_tmpl = res_dir.join("Country.mmdb"); + // if !mmdb_path.exists() && mmdb_tmpl.exists() { + // let _ = fs::copy(mmdb_tmpl, mmdb_path); + // } + + // // copy the wintun.dll + // #[cfg(target_os = "windows")] + // { + // let wintun_path = app_dir.join("wintun.dll"); + // let wintun_tmpl = res_dir.join("wintun.dll"); + // if !wintun_path.exists() && wintun_tmpl.exists() { + // let _ = fs::copy(wintun_tmpl, wintun_path); + // } + // } } diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 16ff18f..e6d8033 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -1,48 +1,48 @@ -use crate::{ - core::{tray, Core}, - data::Data, - utils::init, - utils::server, -}; +use crate::log_err; +use crate::{config::VergeN, core::*, utils::init, utils::server}; use tauri::{App, AppHandle, Manager}; /// handle something when start app -pub fn resolve_setup(app: &App) { - let _ = app - .tray_handle() - .set_menu(tray::Tray::tray_menu(&app.app_handle())); +pub fn resolve_setup(app: &mut App) { + #[cfg(target_os = "macos")] + app.set_activation_policy(tauri::ActivationPolicy::Accessory); + + handle::Handle::global().init(app.app_handle()); init::init_resources(app.package_info()); + // 启动核心 + log_err!(CoreManager::global().init()); + + // log_err!(app + // .tray_handle() + // .set_menu(tray::Tray::tray_menu(&app.app_handle()))); + + log_err!(tray::Tray::update_systray(&app.app_handle())); + + // setup a simple http server for singleton + server::embed_server(app.app_handle()); + let silent_start = { - let global = Data::global(); - let verge = global.verge.lock(); - let singleton = verge.app_singleton_port.clone(); - - // setup a simple http server for singleton - server::embed_server(&app.app_handle(), singleton); - - verge.enable_silent_start.clone().unwrap_or(false) + let verge = VergeN::global().config.lock(); + verge.enable_silent_start.clone() }; - - // core should be initialized after init_app fix #122 - let core = Core::global(); - core.init(app.app_handle()); - - if !silent_start { + if !silent_start.unwrap_or(false) { create_window(&app.app_handle()); } + + log_err!(sysopt::Sysopt::global().init_launch()); + log_err!(sysopt::Sysopt::global().init_sysproxy()); + + log_err!(handle::Handle::update_systray_part()); + log_err!(hotkey::Hotkey::global().init(app.app_handle())); + log_err!(timer::Timer::global().init()); } /// reset system proxy pub fn resolve_reset() { - let core = Core::global(); - let mut sysopt = core.sysopt.lock(); - crate::log_if_err!(sysopt.reset_sysproxy()); - drop(sysopt); - - let mut service = core.service.lock(); - crate::log_if_err!(service.stop()); + log_err!(sysopt::Sysopt::global().reset_sysproxy()); + log_err!(CoreManager::global().stop_core()); } /// create main window diff --git a/src-tauri/src/utils/server.rs b/src-tauri/src/utils/server.rs index c0162c6..1e68326 100644 --- a/src-tauri/src/utils/server.rs +++ b/src-tauri/src/utils/server.rs @@ -1,20 +1,14 @@ extern crate warp; use super::resolve; -use crate::data::Verge; +use crate::config::VergeN; use port_scanner::local_port_available; use tauri::AppHandle; use warp::Filter; -#[cfg(not(feature = "verge-dev"))] -const SERVER_PORT: u16 = 33331; -#[cfg(feature = "verge-dev")] -const SERVER_PORT: u16 = 11233; - /// check whether there is already exists pub fn check_singleton() -> Result<(), ()> { - let verge = Verge::new(); - let port = verge.app_singleton_port.unwrap_or(SERVER_PORT); + let port = VergeN::get_singleton_port(); if !local_port_available(port) { tauri::async_runtime::block_on(async { @@ -29,14 +23,14 @@ pub fn check_singleton() -> Result<(), ()> { /// The embed server only be used to implement singleton process /// maybe it can be used as pac server later -pub fn embed_server(app_handle: &AppHandle, port: Option) { +pub fn embed_server(app_handle: AppHandle) { let app_handle = app_handle.clone(); - let port = port.unwrap_or(SERVER_PORT); + let port = VergeN::get_singleton_port(); tauri::async_runtime::spawn(async move { let commands = warp::path!("commands" / "visible").map(move || { resolve::create_window(&app_handle); - return format!("ok"); + format!("ok") }); warp::serve(commands).bind(([127, 0, 0, 1], port)).await; diff --git a/src/services/cmds.ts b/src/services/cmds.ts index d99c60a..9ab45fe 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -136,9 +136,9 @@ export async function restartSidecar() { return invoke("restart_sidecar"); } -export async function killSidecar() { - return invoke("kill_sidecar"); -} +// export async function killSidecar() { +// return invoke("kill_sidecar"); +// } export async function openAppDir() { return invoke("open_app_dir").catch((err) =>