cmds.rs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. use crate::{
  2. core::{ClashInfo, ProfileItem, Profiles, VergeConfig},
  3. states::{ClashState, ProfilesState, VergeState},
  4. utils::{dirs::app_home_dir, fetch::fetch_profile, sysopt::SysProxyConfig},
  5. };
  6. use serde_yaml::Mapping;
  7. use std::{path::PathBuf, process::Command};
  8. use tauri::{api, State};
  9. /// get all profiles from `profiles.yaml`
  10. /// do not acquire the lock of ProfileLock
  11. #[tauri::command]
  12. pub fn get_profiles(profiles_state: State<'_, ProfilesState>) -> Result<Profiles, String> {
  13. match profiles_state.0.lock() {
  14. Ok(profiles) => Ok(profiles.clone()),
  15. Err(_) => Err("failed to get profiles lock".into()),
  16. }
  17. }
  18. /// synchronize data irregularly
  19. #[tauri::command]
  20. pub fn sync_profiles(profiles_state: State<'_, ProfilesState>) -> Result<(), String> {
  21. match profiles_state.0.lock() {
  22. Ok(mut profiles) => profiles.sync_file(),
  23. Err(_) => Err("failed to get profiles lock".into()),
  24. }
  25. }
  26. /// import the profile from url
  27. /// and save to `profiles.yaml`
  28. #[tauri::command]
  29. pub async fn import_profile(
  30. url: String,
  31. with_proxy: bool,
  32. profiles_state: State<'_, ProfilesState>,
  33. ) -> Result<(), String> {
  34. let result = fetch_profile(&url, with_proxy).await?;
  35. let mut profiles = profiles_state.0.lock().unwrap();
  36. profiles.import_from_url(url, result)
  37. }
  38. /// new a profile
  39. /// append a temp profile item file to the `profiles` dir
  40. /// view the temp profile file by using vscode or other editor
  41. #[tauri::command]
  42. pub async fn new_profile(
  43. name: String,
  44. desc: String,
  45. profiles_state: State<'_, ProfilesState>,
  46. ) -> Result<(), String> {
  47. let mut profiles = profiles_state.0.lock().unwrap();
  48. profiles.append_item(name, desc)?;
  49. Ok(())
  50. }
  51. /// Update the profile
  52. #[tauri::command]
  53. pub async fn update_profile(
  54. index: usize,
  55. with_proxy: bool,
  56. clash_state: State<'_, ClashState>,
  57. profiles_state: State<'_, ProfilesState>,
  58. ) -> Result<(), String> {
  59. // maybe we can get the url from the web app directly
  60. let url = match profiles_state.0.lock() {
  61. Ok(mut profile) => {
  62. let items = profile.items.take().unwrap_or(vec![]);
  63. if index >= items.len() {
  64. return Err("the index out of bound".into());
  65. }
  66. let url = match &items[index].url {
  67. Some(u) => u.clone(),
  68. None => return Err("failed to update profile for `invalid url`".into()),
  69. };
  70. profile.items = Some(items);
  71. url
  72. }
  73. Err(_) => return Err("failed to get profiles lock".into()),
  74. };
  75. let result = fetch_profile(&url, with_proxy).await?;
  76. match profiles_state.0.lock() {
  77. Ok(mut profiles) => {
  78. profiles.update_item(index, result)?;
  79. // reactivate the profile
  80. let current = profiles.current.clone().unwrap_or(0);
  81. if current == index {
  82. let clash = clash_state.0.lock().unwrap();
  83. profiles.activate(&clash)
  84. } else {
  85. Ok(())
  86. }
  87. }
  88. Err(_) => Err("failed to get profiles lock".into()),
  89. }
  90. }
  91. /// change the current profile
  92. #[tauri::command]
  93. pub fn select_profile(
  94. index: usize,
  95. clash_state: State<'_, ClashState>,
  96. profiles_state: State<'_, ProfilesState>,
  97. ) -> Result<(), String> {
  98. let mut profiles = profiles_state.0.lock().unwrap();
  99. match profiles.put_current(index) {
  100. Ok(()) => {
  101. let clash = clash_state.0.lock().unwrap();
  102. profiles.activate(&clash)
  103. }
  104. Err(err) => Err(err),
  105. }
  106. }
  107. /// delete profile item
  108. #[tauri::command]
  109. pub fn delete_profile(
  110. index: usize,
  111. clash_state: State<'_, ClashState>,
  112. profiles_state: State<'_, ProfilesState>,
  113. ) -> Result<(), String> {
  114. let mut profiles = profiles_state.0.lock().unwrap();
  115. match profiles.delete_item(index) {
  116. Ok(change) => match change {
  117. true => {
  118. let clash = clash_state.0.lock().unwrap();
  119. profiles.activate(&clash)
  120. }
  121. false => Ok(()),
  122. },
  123. Err(err) => Err(err),
  124. }
  125. }
  126. /// patch the profile config
  127. #[tauri::command]
  128. pub fn patch_profile(
  129. index: usize,
  130. profile: ProfileItem,
  131. profiles_state: State<'_, ProfilesState>,
  132. ) -> Result<(), String> {
  133. match profiles_state.0.lock() {
  134. Ok(mut profiles) => profiles.patch_item(index, profile),
  135. Err(_) => Err("can not get profiles lock".into()),
  136. }
  137. }
  138. /// run vscode command to edit the profile
  139. #[tauri::command]
  140. pub fn view_profile(index: usize, profiles_state: State<'_, ProfilesState>) -> Result<(), String> {
  141. let mut profiles = profiles_state.0.lock().unwrap();
  142. let items = profiles.items.take().unwrap_or(vec![]);
  143. if index >= items.len() {
  144. profiles.items = Some(items);
  145. return Err("the index out of bound".into());
  146. }
  147. let file = items[index].file.clone().unwrap_or("".into());
  148. profiles.items = Some(items);
  149. let path = app_home_dir().join("profiles").join(file);
  150. if !path.exists() {
  151. return Err("the file not found".into());
  152. }
  153. // use vscode first
  154. if let Ok(code) = which::which("code") {
  155. return match Command::new(code).arg(path).spawn() {
  156. Ok(_) => Ok(()),
  157. Err(_) => Err("failed to open file by VScode".into()),
  158. };
  159. }
  160. open_path_cmd(path, "failed to open file by `open`")
  161. }
  162. /// restart the sidecar
  163. #[tauri::command]
  164. pub fn restart_sidecar(
  165. clash_state: State<'_, ClashState>,
  166. profiles_state: State<'_, ProfilesState>,
  167. ) -> Result<(), String> {
  168. let mut clash = clash_state.0.lock().unwrap();
  169. let mut profiles = profiles_state.0.lock().unwrap();
  170. match clash.restart_sidecar(&mut profiles) {
  171. Ok(_) => Ok(()),
  172. Err(err) => {
  173. log::error!("{}", err);
  174. Err(err)
  175. }
  176. }
  177. }
  178. /// get the clash core info from the state
  179. /// the caller can also get the infomation by clash's api
  180. #[tauri::command]
  181. pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result<ClashInfo, String> {
  182. match clash_state.0.lock() {
  183. Ok(clash) => Ok(clash.info.clone()),
  184. Err(_) => Err("failed to get clash lock".into()),
  185. }
  186. }
  187. /// update the clash core config
  188. /// after putting the change to the clash core
  189. /// then we should save the latest config
  190. #[tauri::command]
  191. pub fn patch_clash_config(
  192. payload: Mapping,
  193. clash_state: State<'_, ClashState>,
  194. verge_state: State<'_, VergeState>,
  195. profiles_state: State<'_, ProfilesState>,
  196. ) -> Result<(), String> {
  197. let mut clash = clash_state.0.lock().unwrap();
  198. let mut verge = verge_state.0.lock().unwrap();
  199. let mut profiles = profiles_state.0.lock().unwrap();
  200. clash.patch_config(payload, &mut verge, &mut profiles)
  201. }
  202. /// get the system proxy
  203. #[tauri::command]
  204. pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
  205. match SysProxyConfig::get_sys() {
  206. Ok(value) => Ok(value),
  207. Err(err) => Err(err.to_string()),
  208. }
  209. }
  210. /// get the current proxy config
  211. /// which may not the same as system proxy
  212. #[tauri::command]
  213. pub fn get_cur_proxy(verge_state: State<'_, VergeState>) -> Result<Option<SysProxyConfig>, String> {
  214. match verge_state.0.lock() {
  215. Ok(verge) => Ok(verge.cur_sysproxy.clone()),
  216. Err(_) => Err("failed to get verge lock".into()),
  217. }
  218. }
  219. /// get the verge config
  220. #[tauri::command]
  221. pub fn get_verge_config(verge_state: State<'_, VergeState>) -> Result<VergeConfig, String> {
  222. let verge = verge_state.0.lock().unwrap();
  223. let mut config = verge.config.clone();
  224. if config.system_proxy_bypass.is_none() && verge.cur_sysproxy.is_some() {
  225. config.system_proxy_bypass = Some(verge.cur_sysproxy.clone().unwrap().bypass)
  226. }
  227. Ok(config)
  228. }
  229. /// patch the verge config
  230. /// this command only save the config and not responsible for other things
  231. #[tauri::command]
  232. pub fn patch_verge_config(
  233. payload: VergeConfig,
  234. verge_state: State<'_, VergeState>,
  235. ) -> Result<(), String> {
  236. let mut verge = verge_state.0.lock().unwrap();
  237. verge.patch_config(payload)
  238. }
  239. /// kill all sidecars when update app
  240. #[tauri::command]
  241. pub fn kill_sidecars() {
  242. api::process::kill_children();
  243. }
  244. /// open app config dir
  245. #[tauri::command]
  246. pub fn open_app_dir() -> Result<(), String> {
  247. let app_dir = app_home_dir();
  248. open_path_cmd(app_dir, "failed to open app dir")
  249. }
  250. /// open logs dir
  251. #[tauri::command]
  252. pub fn open_logs_dir() -> Result<(), String> {
  253. let log_dir = app_home_dir().join("logs");
  254. open_path_cmd(log_dir, "failed to open logs dir")
  255. }
  256. /// get open/explorer command
  257. fn open_path_cmd(dir: PathBuf, err_str: &str) -> Result<(), String> {
  258. #[cfg(target_os = "windows")]
  259. {
  260. use std::os::windows::process::CommandExt;
  261. match Command::new("explorer")
  262. .creation_flags(0x08000000)
  263. .arg(dir)
  264. .spawn()
  265. {
  266. Ok(_) => Ok(()),
  267. Err(_) => Err(err_str.into()),
  268. }
  269. }
  270. #[cfg(not(target_os = "windows"))]
  271. match Command::new("open").arg(dir).spawn() {
  272. Ok(_) => Ok(()),
  273. Err(_) => Err(err_str.into()),
  274. }
  275. }