refactor: impl as struct methods

This commit is contained in:
GyDi 2022-01-05 02:00:59 +08:00
parent e32bfd9aab
commit 03f9fa4bc2
8 changed files with 290 additions and 201 deletions

View File

@ -1,60 +1,42 @@
use crate::{ use crate::{
config::{ProfileItem, ProfilesConfig}, config::{ProfileItem, ProfilesConfig},
events::state::{ClashInfoState, ProfileLock}, events::state::{ClashInfoState, ProfilesState},
utils::{ utils::{clash, fetch},
app_home_dir,
clash::put_clash_profile,
config::{read_profiles, save_profiles},
fetch::fetch_profile,
},
}; };
use std::fs::File;
use std::io::Write;
use std::time::{SystemTime, UNIX_EPOCH};
use tauri::State; use tauri::State;
/// get all profiles from `profiles.yaml`
/// do not acquire the lock of ProfileLock
#[tauri::command]
pub fn get_profiles(profiles: State<'_, ProfilesState>) -> Result<ProfilesConfig, String> {
match profiles.0.lock() {
Ok(profiles) => Ok(profiles.clone()),
Err(_) => Err("can not get profiles lock".into()),
}
}
/// synchronize data irregularly
#[tauri::command]
pub fn sync_profiles(profiles: State<'_, ProfilesState>) -> Result<(), String> {
match profiles.0.lock() {
Ok(mut profiles) => profiles.sync_file(),
Err(_) => Err("can not get profiles lock".into()),
}
}
/// Import the profile from url /// Import the profile from url
/// and save to `profiles.yaml` /// and save to `profiles.yaml`
#[tauri::command] #[tauri::command]
pub async fn import_profile(url: String, lock: State<'_, ProfileLock>) -> Result<(), String> { pub async fn import_profile(url: String, profiles: State<'_, ProfilesState>) -> Result<(), String> {
let result = match fetch_profile(&url).await { let result = match fetch::fetch_profile(&url).await {
Some(r) => r, Some(r) => r,
None => { None => return Err(format!("failed to fetch profile from `{}`", url)),
log::error!("failed to fetch profile from `{}`", url);
return Err(format!("failed to fetch profile from `{}`", url));
}
}; };
// get lock match profiles.0.lock() {
if lock.0.lock().is_err() { Ok(mut profiles) => profiles.import_from_url(url, result),
return Err(format!("can not get file lock")); Err(_) => Err("can not get profiles lock".into()),
} }
// 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![]);
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
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),
updated: Some(now as usize),
});
profiles.items = Some(items);
save_profiles(&profiles)
} }
/// Update the profile /// Update the profile
@ -62,129 +44,78 @@ pub async fn import_profile(url: String, lock: State<'_, ProfileLock>) -> Result
/// http request firstly /// http request firstly
/// then acquire the lock of `profiles.yaml` /// then acquire the lock of `profiles.yaml`
#[tauri::command] #[tauri::command]
pub async fn update_profile(index: usize, lock: State<'_, ProfileLock>) -> Result<(), String> { pub async fn update_profile(
// get lock index: usize,
if lock.0.lock().is_err() { profiles: State<'_, ProfilesState>,
return Err(format!("can not get file lock")); ) -> Result<(), String> {
} // maybe we can get the url from the web app directly
let url = {
// update `profiles.yaml` match profiles.0.lock() {
let mut profiles = read_profiles(); Ok(mut profile) => {
let mut items = profiles.items.unwrap_or(vec![]); let items = profile.items.take().unwrap_or(vec![]);
if index >= items.len() {
if index >= items.len() { return Err("the index out of bound".into());
return Err(format!("the index out of bound")); }
} let url = match &items[index].url {
Some(u) => u.clone(),
let url = match &items[index].url { None => return Err("failed to update profile for `invalid url`".into()),
Some(u) => u, };
None => return Err(format!("invalid url")), profile.items = Some(items);
}; url
}
let result = match fetch_profile(&url).await { Err(_) => return Err("can not get profiles lock".into()),
Some(r) => r,
None => {
log::error!("failed to fetch profile from `{}`", url);
return Err(format!("failed to fetch profile from `{}`", url));
} }
}; };
let now = SystemTime::now() let result = match fetch::fetch_profile(&url).await {
.duration_since(UNIX_EPOCH) Some(r) => r,
.unwrap() None => return Err(format!("failed to fetch profile from `{}`", url)),
.as_secs() as usize; };
// update file match profiles.0.lock() {
let file_path = &items[index].file.as_ref().unwrap(); Ok(mut profiles) => profiles.update_item(index, result),
let file_path = app_home_dir().join("profiles").join(file_path); Err(_) => Err("can not get profiles lock".into()),
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);
items[index].updated = Some(now);
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 /// change the current profile
#[tauri::command] #[tauri::command]
pub async fn put_profiles( pub async fn select_profile(
current: usize, index: usize,
lock: State<'_, ProfileLock>, profiles: State<'_, ProfilesState>,
clash_info: State<'_, ClashInfoState>, clash_info: State<'_, ClashInfoState>,
) -> Result<(), String> { ) -> Result<(), String> {
if lock.0.lock().is_err() { match profiles.0.lock() {
return Err(format!("can not get file lock")); Ok(mut profiles) => profiles.put_current(index)?,
} Err(_) => return Err("can not get profiles lock".into()),
};
let clash_info = match clash_info.0.lock() { let arc = match clash_info.0.lock() {
Ok(arc) => arc.clone(), Ok(arc) => arc.clone(),
_ => return Err(format!("can not get clash info")), _ => return Err("can not get clash info lock".into()),
}; };
let mut profiles = read_profiles(); clash::put_clash_profile(&arc).await
let items_len = match &profiles.items { }
Some(list) => list.len(),
None => 0,
};
if current >= items_len { /// delete profile item
return Err(format!("the index out of bound")); #[tauri::command]
} pub fn delete_profile(index: usize, profiles: State<'_, ProfilesState>) -> Result<(), String> {
match profiles.0.lock() {
profiles.current = Some(current); Ok(mut profiles) => profiles.delete_item(index),
match save_profiles(&profiles) { Err(_) => Err("can not get profiles lock".into()),
Ok(_) => put_clash_profile(&clash_info).await, }
Err(err) => Err(err), }
/// patch the profile config
#[tauri::command]
pub fn patch_profile(
index: usize,
profile: ProfileItem,
profiles: State<'_, ProfilesState>,
) -> Result<(), String> {
match profiles.0.lock() {
Ok(mut profiles) => profiles.patch_item(index, profile),
Err(_) => Err("can not get profiles lock".into()),
} }
} }

View File

@ -1,4 +1,8 @@
use crate::utils::{app_home_dir, config};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::Write;
use std::time::{SystemTime, UNIX_EPOCH};
/// Define the `profiles.yaml` schema /// Define the `profiles.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)] #[derive(Default, Debug, Clone, Deserialize, Serialize)]
@ -50,3 +54,156 @@ pub struct ProfileResponse {
pub data: String, pub data: String,
pub extra: ProfileExtra, pub extra: ProfileExtra,
} }
static PROFILE_YAML: &str = "profiles.yaml";
impl ProfilesConfig {
/// read the config from the file
pub fn read_file() -> Self {
config::read_yaml::<ProfilesConfig>(app_home_dir().join(PROFILE_YAML))
}
/// save the config to the file
pub fn save_file(&self) -> Result<(), String> {
config::save_yaml(
app_home_dir().join(PROFILE_YAML),
self,
Some("# Profiles Config for Clash Verge\n\n"),
)
}
/// sync the config between file and memory
pub fn sync_file(&mut self) -> Result<(), String> {
let data = config::read_yaml::<Self>(app_home_dir().join(PROFILE_YAML));
if data.current.is_none() {
Err("failed to read profiles.yaml".into())
} else {
self.current = data.current;
self.items = data.items;
Ok(())
}
}
/// import the new profile from the url
/// and update the config file
pub fn import_from_url(&mut self, url: String, result: ProfileResponse) -> Result<(), String> {
// 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 data = ProfilesConfig::read_file();
let mut items = data.items.unwrap_or(vec![]);
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
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),
updated: Some(now as usize),
});
self.items = Some(items);
if data.current.is_none() {
self.current = Some(0);
}
self.save_file()
}
/// set the current and save to file
pub fn put_current(&mut self, index: usize) -> Result<(), String> {
let items = self.items.take().unwrap_or(vec![]);
if index >= items.len() {
return Err("the index out of bound".into());
}
self.items = Some(items);
self.current = Some(index);
self.save_file()
}
/// update the target profile
/// and save to config file
/// only support the url item
pub fn update_item(&mut self, index: usize, result: ProfileResponse) -> Result<(), String> {
let mut items = self.items.take().unwrap_or(vec![]);
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as usize;
// 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);
items[index].updated = Some(now);
self.items = Some(items);
self.save_file()
}
/// patch item
pub fn patch_item(&mut self, index: usize, profile: ProfileItem) -> Result<(), String> {
let mut items = self.items.take().unwrap_or(vec![]);
if index >= items.len() {
return Err("index out of bound".into());
}
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;
}
self.items = Some(items);
self.save_file()
}
/// delete the item
pub fn delete_item(&mut self, index: usize) -> Result<(), String> {
let mut current = self.current.clone().unwrap_or(0);
let mut items = self.items.clone().unwrap_or(vec![]);
if index >= items.len() {
return Err("index out of bound".into());
}
items.remove(index);
if current == index {
current = 0;
} else if current > index {
current = current - 1;
}
self.current = Some(current);
self.save_file()
}
}

View File

@ -1,5 +1,8 @@
use super::emit::ClashInfoPayload; use super::emit::ClashInfoPayload;
use crate::{config::VergeConfig, utils::sysopt::SysProxyConfig}; use crate::{
config::{ProfilesConfig, VergeConfig},
utils::sysopt::SysProxyConfig,
};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use tauri::api::process::CommandChild; use tauri::api::process::CommandChild;
@ -7,7 +10,7 @@ use tauri::api::process::CommandChild;
pub struct ClashInfoState(pub Arc<Mutex<ClashInfoPayload>>); pub struct ClashInfoState(pub Arc<Mutex<ClashInfoPayload>>);
#[derive(Default)] #[derive(Default)]
pub struct ProfileLock(pub Mutex<bool>); pub struct ProfilesState(pub Arc<Mutex<ProfilesConfig>>);
#[derive(Default)] #[derive(Default)]
pub struct VergeConfLock(pub Arc<Mutex<VergeConfig>>); pub struct VergeConfLock(pub Arc<Mutex<VergeConfig>>);

View File

@ -35,7 +35,7 @@ fn main() -> std::io::Result<()> {
.manage(state::VergeConfLock::default()) .manage(state::VergeConfLock::default())
.manage(state::ClashInfoState::default()) .manage(state::ClashInfoState::default())
.manage(state::SomthingState::default()) .manage(state::SomthingState::default())
.manage(state::ProfileLock::default()) .manage(state::ProfilesState::default())
.setup(|app| Ok(resolve::resolve_setup(app))) .setup(|app| Ok(resolve::resolve_setup(app)))
.system_tray(SystemTray::new().with_menu(menu)) .system_tray(SystemTray::new().with_menu(menu))
.on_system_tray_event(move |app_handle, event| match event { .on_system_tray_event(move |app_handle, event| match event {
@ -87,9 +87,11 @@ fn main() -> std::io::Result<()> {
cmds::some::patch_verge_config, cmds::some::patch_verge_config,
cmds::profile::import_profile, cmds::profile::import_profile,
cmds::profile::update_profile, cmds::profile::update_profile,
cmds::profile::delete_profile,
cmds::profile::select_profile,
cmds::profile::patch_profile,
cmds::profile::sync_profiles,
cmds::profile::get_profiles, cmds::profile::get_profiles,
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

@ -1,5 +1,5 @@
use crate::{ use crate::{
config::{ClashController, ProfilesConfig, VergeConfig}, config::{ClashController, VergeConfig},
utils::app_home_dir, utils::app_home_dir,
}; };
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
@ -103,20 +103,6 @@ pub fn read_clash_controller() -> ClashController {
} }
} }
/// Get Profiles Config
pub fn read_profiles() -> ProfilesConfig {
read_yaml::<ProfilesConfig>(app_home_dir().join("profiles.yaml"))
}
/// Save Verge App Config
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"),
)
}
/// Get the `verge.yaml` /// Get the `verge.yaml`
pub fn read_verge() -> VergeConfig { pub fn read_verge() -> VergeConfig {
read_yaml::<VergeConfig>(app_home_dir().join("verge.yaml")) read_yaml::<VergeConfig>(app_home_dir().join("verge.yaml"))

View File

@ -22,8 +22,7 @@ import {
} from "@mui/icons-material"; } from "@mui/icons-material";
import { updateProxy } from "../services/api"; import { updateProxy } from "../services/api";
import { ApiType } from "../services/types"; import { ApiType } from "../services/types";
import { getProfiles, setProfiles } from "../services/cmds"; import { getProfiles, patchProfile } from "../services/cmds";
import noop from "../utils/noop";
interface ItemProps { interface ItemProps {
proxy: ApiType.ProxyItem; proxy: ApiType.ProxyItem;
@ -105,7 +104,7 @@ const ProxyGroup = ({ group }: Props) => {
profile.selected[index] = { name: group.name, now: name }; profile.selected[index] = { name: group.name, now: name };
} }
setProfiles(profiles.current!, profile).catch(console.error); patchProfile(profiles.current!, profile).catch(console.error);
} catch { } catch {
setNow(oldValue); setNow(oldValue);
// Todo // Todo

View File

@ -3,8 +3,8 @@ import useSWR, { useSWRConfig } from "swr";
import { Box, Button, Grid, TextField, Typography } from "@mui/material"; import { Box, Button, Grid, TextField, Typography } from "@mui/material";
import { import {
getProfiles, getProfiles,
putProfiles, selectProfile,
setProfiles, patchProfile,
importProfile, importProfile,
} from "../services/cmds"; } from "../services/cmds";
import { getProxies, updateProxy } from "../services/api"; import { getProxies, updateProxy } from "../services/api";
@ -52,7 +52,7 @@ const ProfilePage = () => {
name, name,
now, now,
})); }));
setProfiles(profiles.current!, profile).catch(console.error); patchProfile(profiles.current!, profile).catch(console.error);
// update proxies cache // update proxies cache
if (hasChange) mutate("getProxies", getProxies()); if (hasChange) mutate("getProxies", getProxies());
}); });
@ -66,7 +66,7 @@ const ProfilePage = () => {
try { try {
await importProfile(url); await importProfile(url);
mutate("getProfiles", getProfiles()); mutate("getProfiles", getProfiles());
if (!profiles.items?.length) putProfiles(0).catch(noop); if (!profiles.items?.length) selectProfile(0).catch(noop);
notice.success("Successfully import profile."); notice.success("Successfully import profile.");
} catch { } catch {
notice.error("Failed to import profile."); notice.error("Failed to import profile.");
@ -80,7 +80,7 @@ const ProfilePage = () => {
if (index === profiles.current || lockRef.current) return; if (index === profiles.current || lockRef.current) return;
if (lockRef.current) return; if (lockRef.current) return;
lockRef.current = true; lockRef.current = true;
putProfiles(index) selectProfile(index)
.then(() => { .then(() => {
mutate("getProfiles", { ...profiles, current: index }, true); mutate("getProfiles", { ...profiles, current: index }, true);
}) })

View File

@ -1,6 +1,37 @@
import { invoke } from "@tauri-apps/api/tauri"; import { invoke } from "@tauri-apps/api/tauri";
import { ApiType, CmdType } from "./types"; import { ApiType, CmdType } from "./types";
export async function getProfiles() {
return (await invoke<CmdType.ProfilesConfig>("get_profiles")) ?? {};
}
export async function syncProfiles() {
return invoke<void>("sync_profiles");
}
export async function importProfile(url: string) {
return invoke<void>("import_profile", { url });
}
export async function updateProfile(index: number) {
return invoke<void>("update_profile", { index });
}
export async function deleteProfile(index: number) {
return invoke<void>("delete_profile", { index });
}
export async function patchProfile(
index: number,
profile: CmdType.ProfileItem
) {
return invoke<void>("patch_profile", { index, profile });
}
export async function selectProfile(index: number) {
return invoke<void>("select_profile", { index });
}
export async function restartSidecar() { export async function restartSidecar() {
return invoke<void>("restart_sidecar"); return invoke<void>("restart_sidecar");
} }
@ -13,26 +44,6 @@ export async function patchClashConfig(payload: Partial<ApiType.ConfigData>) {
return invoke<void>("patch_clash_config", { payload }); return invoke<void>("patch_clash_config", { payload });
} }
export async function importProfile(url: string) {
return invoke<void>("import_profile", { url });
}
export async function updateProfile(index: number) {
return invoke<void>("update_profile", { index });
}
export async function getProfiles() {
return (await invoke<CmdType.ProfilesConfig>("get_profiles")) ?? {};
}
export async function setProfiles(index: number, profile: CmdType.ProfileItem) {
return invoke<void>("set_profiles", { index, profile });
}
export async function putProfiles(current: number) {
return invoke<void>("put_profiles", { current });
}
export async function setSysProxy(enable: boolean) { export async function setSysProxy(enable: boolean) {
return invoke<void>("set_sys_proxy", { enable }); return invoke<void>("set_sys_proxy", { enable });
} }