feat: refactor commands and support update profile

This commit is contained in:
GyDi 2021-12-18 02:01:02 +08:00
parent c1bcfc6785
commit 98378e6261
9 changed files with 280 additions and 229 deletions

View File

@ -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<ClashInfoPayload> {
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<String, String> {
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<ProfilesConfig> {
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")),
}
}

View File

@ -0,0 +1,2 @@
pub mod profile;
pub mod some;

View File

@ -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<ProfilesConfig, String> {
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),
}
}

View File

@ -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<ClashInfoPayload, String> {
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")),
}
}

View File

@ -99,13 +99,12 @@ pub fn read_profiles() -> ProfilesConfig {
} }
/// Save Verge App Config /// Save Verge App Config
pub fn save_profiles(profiles: &ProfilesConfig) { pub fn save_profiles(profiles: &ProfilesConfig) -> Result<(), String> {
save_yaml( save_yaml(
app_home_dir().join("profiles.yaml"), app_home_dir().join("profiles.yaml"),
profiles, profiles,
Some("# Profiles Config for Clash Verge\n\n"), Some("# Profiles Config for Clash Verge\n\n"),
) )
.unwrap();
} }
#[test] #[test]

View File

@ -5,7 +5,7 @@
extern crate tauri; extern crate tauri;
mod cmd; mod cmds;
mod config; mod config;
mod events; mod events;
mod utils; mod utils;
@ -57,13 +57,14 @@ fn main() -> std::io::Result<()> {
_ => {} _ => {}
}) })
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
cmd::restart_sidebar, cmds::some::restart_sidecar,
cmd::get_clash_info, cmds::some::get_clash_info,
cmd::import_profile, cmds::some::set_sys_proxy,
cmd::get_profiles, cmds::profile::import_profile,
cmd::set_profiles, cmds::profile::update_profile,
cmd::put_profiles, cmds::profile::get_profiles,
cmd::set_sys_proxy, cmds::profile::set_profiles,
cmds::profile::put_profiles,
]) ])
.build(tauri::generate_context!()) .build(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

View File

@ -28,10 +28,11 @@ interface Props {
selected: boolean; selected: boolean;
itemData: ProfileItem; itemData: ProfileItem;
onClick: () => void; onClick: () => void;
onUpdate: () => void;
} }
const ProfileItemComp: React.FC<Props> = (props) => { const ProfileItemComp: React.FC<Props> = (props) => {
const { selected, itemData, onClick } = props; const { selected, itemData, onClick, onUpdate } = props;
const { name = "Profile", extra } = itemData; const { name = "Profile", extra } = itemData;
const { upload = 0, download = 0, total = 0 } = extra ?? {}; const { upload = 0, download = 0, total = 0 } = extra ?? {};
@ -87,6 +88,7 @@ const ProfileItemComp: React.FC<Props> = (props) => {
color="inherit" color="inherit"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
onUpdate();
}} }}
> >
<MenuRounded /> <MenuRounded />

View File

@ -1,7 +1,12 @@
import { useState } from "react"; import { useState } from "react";
import useSWR, { useSWRConfig } from "swr"; import useSWR, { useSWRConfig } from "swr";
import { Box, Button, Grid, TextField, Typography } from "@mui/material"; 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 ProfileItemComp from "../components/profile-item";
import useNotice from "../utils/use-notice"; 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 ( return (
<Box sx={{ width: 0.9, maxWidth: "850px", mx: "auto", mb: 2 }}> <Box sx={{ width: 0.9, maxWidth: "850px", mx: "auto", mb: 2 }}>
<Typography variant="h4" component="h1" sx={{ py: 2, mb: 1 }}> <Typography variant="h4" component="h1" sx={{ py: 2, mb: 1 }}>
@ -66,6 +81,7 @@ const RulesPage = () => {
selected={profiles.current === idx} selected={profiles.current === idx}
itemData={item} itemData={item}
onClick={() => onProfileChange(idx)} onClick={() => onProfileChange(idx)}
onUpdate={() => onUpdateProfile(idx)}
/> />
</Grid> </Grid>
))} ))}

View File

@ -1,7 +1,7 @@
import { invoke } from "@tauri-apps/api/tauri"; import { invoke } from "@tauri-apps/api/tauri";
export async function restartSidecar() { export async function restartSidecar() {
return invoke<void>("restart_sidebar"); return invoke<void>("restart_sidecar");
} }
export interface ClashInfo { export interface ClashInfo {
@ -15,7 +15,11 @@ export async function getClashInfo() {
} }
export async function importProfile(url: string) { export async function importProfile(url: string) {
return invoke<string>("import_profile", { url }); return invoke<void>("import_profile", { url });
}
export async function updateProfile(index: number) {
return invoke<void>("update_profile", { index });
} }
export interface ProfileItem { export interface ProfileItem {