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::{
config::{ProfileItem, ProfilesConfig},
events::state::{ClashInfoState, ProfileLock},
utils::{
app_home_dir,
clash::put_clash_profile,
config::{read_profiles, save_profiles},
fetch::fetch_profile,
},
events::state::{ClashInfoState, ProfilesState},
utils::{clash, fetch},
};
use std::fs::File;
use std::io::Write;
use std::time::{SystemTime, UNIX_EPOCH};
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
/// 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 {
pub async fn import_profile(url: String, profiles: State<'_, ProfilesState>) -> Result<(), String> {
let result = match fetch::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));
}
None => 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"));
match profiles.0.lock() {
Ok(mut profiles) => profiles.import_from_url(url, result),
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
@ -62,129 +44,78 @@ pub async fn import_profile(url: String, lock: State<'_, ProfileLock>) -> Result
/// 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));
}
};
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);
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(
pub async fn update_profile(
index: usize,
profile: ProfileItem,
lock: State<'_, ProfileLock>,
profiles: State<'_, ProfilesState>,
) -> 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![]);
// maybe we can get the url from the web app directly
let url = {
match profiles.0.lock() {
Ok(mut profile) => {
let items = profile.items.take().unwrap_or(vec![]);
if index >= items.len() {
return Err(format!("the index out of bound"));
return Err("the index out of bound".into());
}
let url = match &items[index].url {
Some(u) => u.clone(),
None => return Err("failed to update profile for `invalid url`".into()),
};
profile.items = Some(items);
url
}
Err(_) => return Err("can not get profiles lock".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;
}
let result = match fetch::fetch_profile(&url).await {
Some(r) => r,
None => return Err(format!("failed to fetch profile from `{}`", url)),
};
profiles.items = Some(items);
save_profiles(&profiles)
match profiles.0.lock() {
Ok(mut profiles) => profiles.update_item(index, result),
Err(_) => Err("can not get profiles lock".into()),
}
}
/// change the current profile
#[tauri::command]
pub async fn put_profiles(
current: usize,
lock: State<'_, ProfileLock>,
pub async fn select_profile(
index: usize,
profiles: State<'_, ProfilesState>,
clash_info: State<'_, ClashInfoState>,
) -> Result<(), String> {
if lock.0.lock().is_err() {
return Err(format!("can not get file lock"));
}
match profiles.0.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(),
_ => return Err(format!("can not get clash info")),
_ => return Err("can not get clash info lock".into()),
};
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"));
clash::put_clash_profile(&arc).await
}
profiles.current = Some(current);
match save_profiles(&profiles) {
Ok(_) => put_clash_profile(&clash_info).await,
Err(err) => Err(err),
/// delete profile item
#[tauri::command]
pub fn delete_profile(index: usize, profiles: State<'_, ProfilesState>) -> Result<(), String> {
match profiles.0.lock() {
Ok(mut profiles) => profiles.delete_item(index),
Err(_) => Err("can not get profiles lock".into()),
}
}
/// 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 std::fs::File;
use std::io::Write;
use std::time::{SystemTime, UNIX_EPOCH};
/// Define the `profiles.yaml` schema
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
@ -50,3 +54,156 @@ pub struct ProfileResponse {
pub data: String,
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 crate::{config::VergeConfig, utils::sysopt::SysProxyConfig};
use crate::{
config::{ProfilesConfig, VergeConfig},
utils::sysopt::SysProxyConfig,
};
use std::sync::{Arc, Mutex};
use tauri::api::process::CommandChild;
@ -7,7 +10,7 @@ use tauri::api::process::CommandChild;
pub struct ClashInfoState(pub Arc<Mutex<ClashInfoPayload>>);
#[derive(Default)]
pub struct ProfileLock(pub Mutex<bool>);
pub struct ProfilesState(pub Arc<Mutex<ProfilesConfig>>);
#[derive(Default)]
pub struct VergeConfLock(pub Arc<Mutex<VergeConfig>>);

View File

@ -35,7 +35,7 @@ fn main() -> std::io::Result<()> {
.manage(state::VergeConfLock::default())
.manage(state::ClashInfoState::default())
.manage(state::SomthingState::default())
.manage(state::ProfileLock::default())
.manage(state::ProfilesState::default())
.setup(|app| Ok(resolve::resolve_setup(app)))
.system_tray(SystemTray::new().with_menu(menu))
.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::profile::import_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::set_profiles,
cmds::profile::put_profiles,
])
.build(tauri::generate_context!())
.expect("error while running tauri application")

View File

@ -1,5 +1,5 @@
use crate::{
config::{ClashController, ProfilesConfig, VergeConfig},
config::{ClashController, VergeConfig},
utils::app_home_dir,
};
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`
pub fn read_verge() -> VergeConfig {
read_yaml::<VergeConfig>(app_home_dir().join("verge.yaml"))

View File

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

View File

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

View File

@ -1,6 +1,37 @@
import { invoke } from "@tauri-apps/api/tauri";
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() {
return invoke<void>("restart_sidecar");
}
@ -13,26 +44,6 @@ export async function patchClashConfig(payload: Partial<ApiType.ConfigData>) {
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) {
return invoke<void>("set_sys_proxy", { enable });
}