diff --git a/src-tauri/src/cmd.rs b/src-tauri/src/cmd.rs deleted file mode 100644 index 421c724..0000000 --- a/src-tauri/src/cmd.rs +++ /dev/null @@ -1,215 +0,0 @@ -use crate::{ - config::{read_profiles, save_profiles, ProfileItem, ProfilesConfig}, - events::{ - emit::ClashInfoPayload, - state::{ClashInfoState, ProfileLock}, - }, - utils::{ - app_home_dir, - clash::{self, put_clash_profile}, - fetch::fetch_profile, - sysopt::{set_proxy_config, SysProxyConfig}, - }, -}; -use std::fs::File; -use std::io::Write; -use tauri::{api::process::kill_children, AppHandle, State}; - -#[tauri::command] -pub fn restart_sidebar(app_handle: AppHandle, clash_info: State<'_, ClashInfoState>) { - kill_children(); - let payload = clash::run_clash_bin(&app_handle); - - if let Ok(mut arc) = clash_info.0.lock() { - *arc = payload; - } -} - -#[tauri::command] -pub fn get_clash_info(clash_info: State<'_, ClashInfoState>) -> Option { - match clash_info.0.lock() { - Ok(arc) => Some(arc.clone()), - _ => None, - } -} - -/// Import the Profile from url and -/// save to the `profiles.yaml` file -#[tauri::command] -pub async fn import_profile(url: String, lock: State<'_, ProfileLock>) -> Result { - let result = match fetch_profile(&url).await { - Some(r) => r, - None => { - log::error!("failed to fetch profile from `{}`", url); - return Err(format!("failed")); - } - }; - - let path = app_home_dir().join("profiles").join(&result.file); - File::create(path) - .unwrap() - .write(result.data.as_bytes()) - .unwrap(); - - // get lock - match lock.0.lock() { - Ok(_) => {} - Err(_) => return Err(format!("can not get file locked")), - }; - - // update profiles.yaml - let mut profiles = read_profiles(); - let mut items = match profiles.items { - Some(p) => p, - None => vec![], - }; - - let profile = ProfileItem { - name: Some(result.name), - file: Some(result.file), - mode: Some(format!("rule")), - url: Some(url), - selected: Some(vec![]), // Todo: parse the selected list - extra: Some(result.extra), - }; - - items.push(profile); - profiles.items = Some(items); - save_profiles(&profiles); - - Ok(format!("success")) -} - -#[tauri::command] -pub fn get_profiles(lock: State<'_, ProfileLock>) -> Option { - match lock.0.lock() { - Ok(_) => Some(read_profiles()), - Err(_) => None, - } -} - -#[tauri::command] -/// update the profile config -pub fn set_profiles( - current: usize, - profile: ProfileItem, - lock: State<'_, ProfileLock>, -) -> Result<(), String> { - match lock.0.lock() { - Ok(_) => {} - Err(_) => return Err(format!("can not get file locked")), - }; - - let mut profiles = read_profiles(); - let mut items = match profiles.items { - Some(p) => p, - None => vec![], - }; - - if current >= items.len() { - return Err(format!("out of profiles bound")); - } - - let mut origin = items[current].clone(); - - if profile.name.is_some() { - origin.name = profile.name; - } - if profile.file.is_some() { - origin.file = profile.file; - } - if profile.mode.is_some() { - origin.mode = profile.mode; - } - if profile.url.is_some() { - origin.url = profile.url; - } - if profile.selected.is_some() { - origin.selected = profile.selected; - } - if profile.extra.is_some() { - origin.extra = profile.extra; - } - - items[current] = origin; - profiles.items = Some(items); - save_profiles(&profiles); - - Ok(()) -} - -#[tauri::command] -/// change to target profile -pub async fn put_profiles( - current: usize, - lock: State<'_, ProfileLock>, - clash_info: State<'_, ClashInfoState>, -) -> Result<(), String> { - match lock.0.lock() { - Ok(_) => {} - Err(_) => return Err(format!("can not get file locked")), - }; - - let clash_info = match clash_info.0.lock() { - Ok(arc) => arc.clone(), - _ => return Err(format!("can not get clash info")), - }; - - let mut profiles = read_profiles(); - let items_len = match &profiles.items { - Some(p) => p.len(), - None => 0, - }; - - if current >= items_len { - return Err(format!( - "failed to change profile to the index `{}`", - current - )); - } - - profiles.current = Some(current as u32); - save_profiles(&profiles); - put_clash_profile(&clash_info).await -} - -#[tauri::command] -/// set system proxy -pub fn set_sys_proxy(enable: bool, clash_info: State<'_, ClashInfoState>) -> Result<(), String> { - let clash_info = match clash_info.0.lock() { - Ok(arc) => arc.clone(), - _ => return Err(format!("can not get clash info")), - }; - - let port = match clash_info.controller { - Some(ctrl) => ctrl.port, - None => None, - }; - - if port.is_none() { - return Err(format!("can not get clash core's port")); - } - - let config = if enable { - let server = format!("127.0.0.1:{}", port.unwrap()); - // todo - let bypass = String::from("localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*"); - - SysProxyConfig { - enable, - server, - bypass, - } - } else { - SysProxyConfig { - enable, - server: String::from(""), - bypass: String::from(""), - } - }; - - match set_proxy_config(&config) { - Ok(_) => Ok(()), - Err(_) => Err(format!("can not set proxy")), - } -} diff --git a/src-tauri/src/cmds/mod.rs b/src-tauri/src/cmds/mod.rs new file mode 100644 index 0000000..c5be45b --- /dev/null +++ b/src-tauri/src/cmds/mod.rs @@ -0,0 +1,2 @@ +pub mod profile; +pub mod some; diff --git a/src-tauri/src/cmds/profile.rs b/src-tauri/src/cmds/profile.rs new file mode 100644 index 0000000..26d672c --- /dev/null +++ b/src-tauri/src/cmds/profile.rs @@ -0,0 +1,172 @@ +use crate::{ + config::{read_profiles, save_profiles, ProfileItem, ProfilesConfig}, + events::state::{ClashInfoState, ProfileLock}, + utils::{app_home_dir, clash::put_clash_profile, fetch::fetch_profile}, +}; +use std::fs::File; +use std::io::Write; +use tauri::State; + +/// Import the profile from url +/// and save to `profiles.yaml` +#[tauri::command] +pub async fn import_profile(url: String, lock: State<'_, ProfileLock>) -> Result<(), String> { + let result = match fetch_profile(&url).await { + Some(r) => r, + None => { + log::error!("failed to fetch profile from `{}`", url); + return Err(format!("failed to fetch profile from `{}`", url)); + } + }; + + // get lock + if lock.0.lock().is_err() { + return Err(format!("can not get file lock")); + } + + // save the profile file + let path = app_home_dir().join("profiles").join(&result.file); + let file_data = result.data.as_bytes(); + File::create(path).unwrap().write(file_data).unwrap(); + + // update `profiles.yaml` + let mut profiles = read_profiles(); + let mut items = profiles.items.unwrap_or(vec![]); + + items.push(ProfileItem { + name: Some(result.name), + file: Some(result.file), + mode: Some(format!("rule")), + url: Some(url), + selected: Some(vec![]), + extra: Some(result.extra), + }); + profiles.items = Some(items); + save_profiles(&profiles) +} + +/// Update the profile +/// and save to `profiles.yaml` +/// http request firstly +/// then acquire the lock of `profiles.yaml` +#[tauri::command] +pub async fn update_profile(index: usize, lock: State<'_, ProfileLock>) -> Result<(), String> { + // get lock + if lock.0.lock().is_err() { + return Err(format!("can not get file lock")); + } + + // update `profiles.yaml` + let mut profiles = read_profiles(); + let mut items = profiles.items.unwrap_or(vec![]); + + if index >= items.len() { + return Err(format!("the index out of bound")); + } + + let url = match &items[index].url { + Some(u) => u, + None => return Err(format!("invalid url")), + }; + + let result = match fetch_profile(&url).await { + Some(r) => r, + None => { + log::error!("failed to fetch profile from `{}`", url); + return Err(format!("failed to fetch profile from `{}`", url)); + } + }; + + // update file + let file_path = &items[index].file.as_ref().unwrap(); + let file_path = app_home_dir().join("profiles").join(file_path); + let file_data = result.data.as_bytes(); + File::create(file_path).unwrap().write(file_data).unwrap(); + + items[index].name = Some(result.name); + items[index].extra = Some(result.extra); + profiles.items = Some(items); + save_profiles(&profiles) +} + +/// get all profiles from `profiles.yaml` +/// do not acquire the lock of ProfileLock +#[tauri::command] +pub fn get_profiles() -> Result { + Ok(read_profiles()) +} + +/// patch the profile config +#[tauri::command] +pub fn set_profiles( + index: usize, + profile: ProfileItem, + lock: State<'_, ProfileLock>, +) -> Result<(), String> { + // get lock + if lock.0.lock().is_err() { + return Err(format!("can not get file lock")); + } + + let mut profiles = read_profiles(); + let mut items = profiles.items.unwrap_or(vec![]); + + if index >= items.len() { + return Err(format!("the index out of bound")); + } + + if profile.name.is_some() { + items[index].name = profile.name; + } + if profile.file.is_some() { + items[index].file = profile.file; + } + if profile.mode.is_some() { + items[index].mode = profile.mode; + } + if profile.url.is_some() { + items[index].url = profile.url; + } + if profile.selected.is_some() { + items[index].selected = profile.selected; + } + if profile.extra.is_some() { + items[index].extra = profile.extra; + } + + profiles.items = Some(items); + save_profiles(&profiles) +} + +/// change the current profile +#[tauri::command] +pub async fn put_profiles( + current: usize, + lock: State<'_, ProfileLock>, + clash_info: State<'_, ClashInfoState>, +) -> Result<(), String> { + if lock.0.lock().is_err() { + return Err(format!("can not get file lock")); + } + + let clash_info = match clash_info.0.lock() { + Ok(arc) => arc.clone(), + _ => return Err(format!("can not get clash info")), + }; + + let mut profiles = read_profiles(); + let items_len = match &profiles.items { + Some(list) => list.len(), + None => 0, + }; + + if current >= items_len { + return Err(format!("the index out of bound")); + } + + profiles.current = Some(current as u32); + match save_profiles(&profiles) { + Ok(_) => put_clash_profile(&clash_info).await, + Err(err) => Err(err), + } +} diff --git a/src-tauri/src/cmds/some.rs b/src-tauri/src/cmds/some.rs new file mode 100644 index 0000000..9b7c556 --- /dev/null +++ b/src-tauri/src/cmds/some.rs @@ -0,0 +1,70 @@ +use crate::{ + events::{emit::ClashInfoPayload, state::ClashInfoState}, + utils::{ + clash::run_clash_bin, + sysopt::{set_proxy_config, SysProxyConfig}, + }, +}; +use tauri::{api::process::kill_children, AppHandle, State}; + +/// restart the sidecar +#[tauri::command] +pub fn restart_sidecar(app_handle: AppHandle, clash_info: State<'_, ClashInfoState>) { + kill_children(); + let payload = run_clash_bin(&app_handle); + + if let Ok(mut arc) = clash_info.0.lock() { + *arc = payload; + } +} + +/// get the clash core info from the state +#[tauri::command] +pub fn get_clash_info(clash_info: State<'_, ClashInfoState>) -> Result { + match clash_info.0.lock() { + Ok(arc) => Ok(arc.clone()), + Err(_) => Err(format!("can not get clash info")), + } +} + +/// set the system proxy +/// Tips: only support windows now +#[tauri::command] +pub fn set_sys_proxy(enable: bool, clash_info: State<'_, ClashInfoState>) -> Result<(), String> { + let clash_info = match clash_info.0.lock() { + Ok(arc) => arc.clone(), + _ => return Err(format!("can not get clash info")), + }; + + let port = match clash_info.controller { + Some(ctrl) => ctrl.port, + None => None, + }; + + if port.is_none() { + return Err(format!("can not get clash core's port")); + } + + let config = if enable { + let server = format!("127.0.0.1:{}", port.unwrap()); + // todo + let bypass = String::from("localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*"); + + SysProxyConfig { + enable, + server, + bypass, + } + } else { + SysProxyConfig { + enable, + server: String::from(""), + bypass: String::from(""), + } + }; + + match set_proxy_config(&config) { + Ok(_) => Ok(()), + Err(_) => Err(format!("can not set proxy")), + } +} diff --git a/src-tauri/src/config/operate.rs b/src-tauri/src/config/operate.rs index 388ed74..b060fd5 100644 --- a/src-tauri/src/config/operate.rs +++ b/src-tauri/src/config/operate.rs @@ -99,13 +99,12 @@ pub fn read_profiles() -> ProfilesConfig { } /// Save Verge App Config -pub fn save_profiles(profiles: &ProfilesConfig) { +pub fn save_profiles(profiles: &ProfilesConfig) -> Result<(), String> { save_yaml( app_home_dir().join("profiles.yaml"), profiles, Some("# Profiles Config for Clash Verge\n\n"), ) - .unwrap(); } #[test] diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 3177d69..271769e 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5,7 +5,7 @@ extern crate tauri; -mod cmd; +mod cmds; mod config; mod events; mod utils; @@ -57,13 +57,14 @@ fn main() -> std::io::Result<()> { _ => {} }) .invoke_handler(tauri::generate_handler![ - cmd::restart_sidebar, - cmd::get_clash_info, - cmd::import_profile, - cmd::get_profiles, - cmd::set_profiles, - cmd::put_profiles, - cmd::set_sys_proxy, + cmds::some::restart_sidecar, + cmds::some::get_clash_info, + cmds::some::set_sys_proxy, + cmds::profile::import_profile, + cmds::profile::update_profile, + cmds::profile::get_profiles, + cmds::profile::set_profiles, + cmds::profile::put_profiles, ]) .build(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/components/profile-item.tsx b/src/components/profile-item.tsx index a6c1997..5b00447 100644 --- a/src/components/profile-item.tsx +++ b/src/components/profile-item.tsx @@ -28,10 +28,11 @@ interface Props { selected: boolean; itemData: ProfileItem; onClick: () => void; + onUpdate: () => void; } const ProfileItemComp: React.FC = (props) => { - const { selected, itemData, onClick } = props; + const { selected, itemData, onClick, onUpdate } = props; const { name = "Profile", extra } = itemData; const { upload = 0, download = 0, total = 0 } = extra ?? {}; @@ -87,6 +88,7 @@ const ProfileItemComp: React.FC = (props) => { color="inherit" onClick={(e) => { e.stopPropagation(); + onUpdate(); }} > diff --git a/src/pages/rules.tsx b/src/pages/rules.tsx index 030a0b8..37b6b16 100644 --- a/src/pages/rules.tsx +++ b/src/pages/rules.tsx @@ -1,7 +1,12 @@ import { useState } from "react"; import useSWR, { useSWRConfig } from "swr"; import { Box, Button, Grid, TextField, Typography } from "@mui/material"; -import { getProfiles, importProfile, putProfiles } from "../services/command"; +import { + getProfiles, + importProfile, + putProfiles, + updateProfile, +} from "../services/command"; import ProfileItemComp from "../components/profile-item"; import useNotice from "../utils/use-notice"; @@ -33,6 +38,16 @@ const RulesPage = () => { }); }; + const onUpdateProfile = (index: number) => { + updateProfile(index) + .then(() => { + mutate("getProfiles"); + }) + .catch((err) => { + console.error(err); + }); + }; + return ( @@ -66,6 +81,7 @@ const RulesPage = () => { selected={profiles.current === idx} itemData={item} onClick={() => onProfileChange(idx)} + onUpdate={() => onUpdateProfile(idx)} /> ))} diff --git a/src/services/command.ts b/src/services/command.ts index 7489a45..84045b2 100644 --- a/src/services/command.ts +++ b/src/services/command.ts @@ -1,7 +1,7 @@ import { invoke } from "@tauri-apps/api/tauri"; export async function restartSidecar() { - return invoke("restart_sidebar"); + return invoke("restart_sidecar"); } export interface ClashInfo { @@ -15,7 +15,11 @@ export async function getClashInfo() { } export async function importProfile(url: string) { - return invoke("import_profile", { url }); + return invoke("import_profile", { url }); +} + +export async function updateProfile(index: number) { + return invoke("update_profile", { index }); } export interface ProfileItem {