refactor: use anyhow to handle error

This commit is contained in:
GyDi 2022-02-28 01:34:25 +08:00
parent ade34f5217
commit dbf380a0d1
No known key found for this signature in database
GPG Key ID: 1C95E0D3467B3084
9 changed files with 224 additions and 193 deletions

1
src-tauri/Cargo.lock generated
View File

@ -454,6 +454,7 @@ dependencies = [
name = "clash-verge" name = "clash-verge"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"auto-launch", "auto-launch",
"chrono", "chrono",
"dirs", "dirs",

View File

@ -13,6 +13,7 @@ build = "build.rs"
tauri-build = { version = "1.0.0-rc.3", features = [] } tauri-build = { version = "1.0.0-rc.3", features = [] }
[dependencies] [dependencies]
anyhow = "1.0"
dirs = "4.0.0" dirs = "4.0.0"
dunce = "1.0.2" dunce = "1.0.2"
chrono = "0.4.19" chrono = "0.4.19"

View File

@ -1,29 +1,46 @@
use crate::{ use crate::{
core::{ClashInfo, ProfileItem, Profiles, VergeConfig}, core::{ClashInfo, ProfileItem, Profiles, VergeConfig},
states::{ClashState, ProfilesState, VergeState}, states::{ClashState, ProfilesState, VergeState},
utils::{dirs::app_home_dir, fetch::fetch_profile, sysopt::SysProxyConfig}, utils::{dirs, fetch::fetch_profile, sysopt::SysProxyConfig},
}; };
use anyhow::Result;
use serde_yaml::Mapping; use serde_yaml::Mapping;
use std::{path::PathBuf, process::Command}; use std::{path::PathBuf, process::Command};
use tauri::{api, State}; use tauri::{api, State};
/// wrap the anyhow error
/// transform the error to String
macro_rules! wrap_err {
($stat: expr) => {
match $stat {
Ok(a) => Ok(a),
Err(err) => {
log::error!("{}", err.to_string());
Err(format!("{}", err.to_string()))
}
}
};
}
/// return the string literal error
macro_rules! ret_err {
($str: literal) => {
return Err($str.into())
};
}
/// get all profiles from `profiles.yaml` /// get all profiles from `profiles.yaml`
/// do not acquire the lock of ProfileLock
#[tauri::command] #[tauri::command]
pub fn get_profiles(profiles_state: State<'_, ProfilesState>) -> Result<Profiles, String> { pub fn get_profiles(profiles_state: State<'_, ProfilesState>) -> Result<Profiles, String> {
match profiles_state.0.lock() { let profiles = profiles_state.0.lock().unwrap();
Ok(profiles) => Ok(profiles.clone()), Ok(profiles.clone())
Err(_) => Err("failed to get profiles lock".into()),
}
} }
/// synchronize data irregularly /// synchronize data irregularly
#[tauri::command] #[tauri::command]
pub fn sync_profiles(profiles_state: State<'_, ProfilesState>) -> Result<(), String> { pub fn sync_profiles(profiles_state: State<'_, ProfilesState>) -> Result<(), String> {
match profiles_state.0.lock() { let mut profiles = profiles_state.0.lock().unwrap();
Ok(mut profiles) => profiles.sync_file(), wrap_err!(profiles.sync_file())
Err(_) => Err("failed to get profiles lock".into()),
}
} }
/// import the profile from url /// import the profile from url
@ -36,7 +53,7 @@ pub async fn import_profile(
) -> Result<(), String> { ) -> Result<(), String> {
let result = fetch_profile(&url, with_proxy).await?; let result = fetch_profile(&url, with_proxy).await?;
let mut profiles = profiles_state.0.lock().unwrap(); let mut profiles = profiles_state.0.lock().unwrap();
profiles.import_from_url(url, result) wrap_err!(profiles.import_from_url(url, result))
} }
/// new a profile /// new a profile
@ -49,7 +66,7 @@ pub async fn new_profile(
profiles_state: State<'_, ProfilesState>, profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> { ) -> Result<(), String> {
let mut profiles = profiles_state.0.lock().unwrap(); let mut profiles = profiles_state.0.lock().unwrap();
profiles.append_item(name, desc)?; wrap_err!(profiles.append_item(name, desc))?;
Ok(()) Ok(())
} }
@ -66,34 +83,34 @@ pub async fn update_profile(
Ok(mut profile) => { Ok(mut profile) => {
let items = profile.items.take().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()); ret_err!("the index out of bound");
} }
let url = match &items[index].url { let url = match &items[index].url {
Some(u) => u.clone(), Some(u) => u.clone(),
None => return Err("failed to update profile for `invalid url`".into()), None => ret_err!("failed to update profile for `invalid url`"),
}; };
profile.items = Some(items); profile.items = Some(items);
url url
} }
Err(_) => return Err("failed to get profiles lock".into()), Err(_) => ret_err!("failed to get profiles lock"),
}; };
let result = fetch_profile(&url, with_proxy).await?; let result = fetch_profile(&url, with_proxy).await?;
match profiles_state.0.lock() { match profiles_state.0.lock() {
Ok(mut profiles) => { Ok(mut profiles) => {
profiles.update_item(index, result)?; wrap_err!(profiles.update_item(index, result))?;
// reactivate the profile // reactivate the profile
let current = profiles.current.clone().unwrap_or(0); let current = profiles.current.clone().unwrap_or(0);
if current == index { if current == index {
let clash = clash_state.0.lock().unwrap(); let clash = clash_state.0.lock().unwrap();
profiles.activate(&clash) wrap_err!(profiles.activate(&clash))
} else { } else {
Ok(()) Ok(())
} }
} }
Err(_) => Err("failed to get profiles lock".into()), Err(_) => ret_err!("failed to get profiles lock"),
} }
} }
@ -105,14 +122,10 @@ pub fn select_profile(
profiles_state: State<'_, ProfilesState>, profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> { ) -> Result<(), String> {
let mut profiles = profiles_state.0.lock().unwrap(); let mut profiles = profiles_state.0.lock().unwrap();
wrap_err!(profiles.put_current(index))?;
match profiles.put_current(index) { let clash = clash_state.0.lock().unwrap();
Ok(()) => { wrap_err!(profiles.activate(&clash))
let clash = clash_state.0.lock().unwrap();
profiles.activate(&clash)
}
Err(err) => Err(err),
}
} }
/// delete profile item /// delete profile item
@ -123,16 +136,13 @@ pub fn delete_profile(
profiles_state: State<'_, ProfilesState>, profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> { ) -> Result<(), String> {
let mut profiles = profiles_state.0.lock().unwrap(); let mut profiles = profiles_state.0.lock().unwrap();
match profiles.delete_item(index) {
Ok(change) => match change { if wrap_err!(profiles.delete_item(index))? {
true => { let clash = clash_state.0.lock().unwrap();
let clash = clash_state.0.lock().unwrap(); wrap_err!(profiles.activate(&clash))?;
profiles.activate(&clash)
}
false => Ok(()),
},
Err(err) => Err(err),
} }
Ok(())
} }
/// patch the profile config /// patch the profile config
@ -142,10 +152,8 @@ pub fn patch_profile(
profile: ProfileItem, profile: ProfileItem,
profiles_state: State<'_, ProfilesState>, profiles_state: State<'_, ProfilesState>,
) -> Result<(), String> { ) -> Result<(), String> {
match profiles_state.0.lock() { let mut profiles = profiles_state.0.lock().unwrap();
Ok(mut profiles) => profiles.patch_item(index, profile), wrap_err!(profiles.patch_item(index, profile))
Err(_) => Err("can not get profiles lock".into()),
}
} }
/// run vscode command to edit the profile /// run vscode command to edit the profile
@ -156,19 +164,34 @@ pub fn view_profile(index: usize, profiles_state: State<'_, ProfilesState>) -> R
if index >= items.len() { if index >= items.len() {
profiles.items = Some(items); profiles.items = Some(items);
return Err("the index out of bound".into()); ret_err!("the index out of bound");
} }
let file = items[index].file.clone().unwrap_or("".into()); let file = items[index].file.clone().unwrap_or("".into());
profiles.items = Some(items); profiles.items = Some(items);
let path = app_home_dir().join("profiles").join(file); let path = dirs::app_profiles_dir().join(file);
if !path.exists() { if !path.exists() {
return Err("the file not found".into()); ret_err!("the file not found");
} }
// use vscode first // use vscode first
if let Ok(code) = which::which("code") { if let Ok(code) = which::which("code") {
#[cfg(target_os = "windows")]
{
use std::os::windows::process::CommandExt;
return match Command::new(code)
.creation_flags(0x08000000)
.arg(path)
.spawn()
{
Ok(_) => Ok(()),
Err(_) => Err("failed to open file by VScode".into()),
};
}
#[cfg(not(target_os = "windows"))]
return match Command::new(code).arg(path).spawn() { return match Command::new(code).arg(path).spawn() {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(_) => Err("failed to open file by VScode".into()), Err(_) => Err("failed to open file by VScode".into()),
@ -187,23 +210,15 @@ pub fn restart_sidecar(
let mut clash = clash_state.0.lock().unwrap(); let mut clash = clash_state.0.lock().unwrap();
let mut profiles = profiles_state.0.lock().unwrap(); let mut profiles = profiles_state.0.lock().unwrap();
match clash.restart_sidecar(&mut profiles) { wrap_err!(clash.restart_sidecar(&mut profiles))
Ok(_) => Ok(()),
Err(err) => {
log::error!("{}", err);
Err(err)
}
}
} }
/// get the clash core info from the state /// get the clash core info from the state
/// the caller can also get the infomation by clash's api /// the caller can also get the infomation by clash's api
#[tauri::command] #[tauri::command]
pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result<ClashInfo, String> { pub fn get_clash_info(clash_state: State<'_, ClashState>) -> Result<ClashInfo, String> {
match clash_state.0.lock() { let clash = clash_state.0.lock().unwrap();
Ok(clash) => Ok(clash.info.clone()), Ok(clash.info.clone())
Err(_) => Err("failed to get clash lock".into()),
}
} }
/// update the clash core config /// update the clash core config
@ -219,26 +234,21 @@ pub fn patch_clash_config(
let mut clash = clash_state.0.lock().unwrap(); let mut clash = clash_state.0.lock().unwrap();
let mut verge = verge_state.0.lock().unwrap(); let mut verge = verge_state.0.lock().unwrap();
let mut profiles = profiles_state.0.lock().unwrap(); let mut profiles = profiles_state.0.lock().unwrap();
clash.patch_config(payload, &mut verge, &mut profiles) wrap_err!(clash.patch_config(payload, &mut verge, &mut profiles))
} }
/// get the system proxy /// get the system proxy
#[tauri::command] #[tauri::command]
pub fn get_sys_proxy() -> Result<SysProxyConfig, String> { pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
match SysProxyConfig::get_sys() { wrap_err!(SysProxyConfig::get_sys())
Ok(value) => Ok(value),
Err(err) => Err(err.to_string()),
}
} }
/// get the current proxy config /// get the current proxy config
/// which may not the same as system proxy /// which may not the same as system proxy
#[tauri::command] #[tauri::command]
pub fn get_cur_proxy(verge_state: State<'_, VergeState>) -> Result<Option<SysProxyConfig>, String> { pub fn get_cur_proxy(verge_state: State<'_, VergeState>) -> Result<Option<SysProxyConfig>, String> {
match verge_state.0.lock() { let verge = verge_state.0.lock().unwrap();
Ok(verge) => Ok(verge.cur_sysproxy.clone()), Ok(verge.cur_sysproxy.clone())
Err(_) => Err("failed to get verge lock".into()),
}
} }
/// get the verge config /// get the verge config
@ -266,16 +276,16 @@ pub fn patch_verge_config(
let tun_mode = payload.enable_tun_mode.clone(); let tun_mode = payload.enable_tun_mode.clone();
let mut verge = verge_state.0.lock().unwrap(); let mut verge = verge_state.0.lock().unwrap();
verge.patch_config(payload)?; wrap_err!(verge.patch_config(payload))?;
// change tun mode // change tun mode
if tun_mode.is_some() { if tun_mode.is_some() {
let mut clash = clash_state.0.lock().unwrap(); let mut clash = clash_state.0.lock().unwrap();
let profiles = profiles_state.0.lock().unwrap(); let profiles = profiles_state.0.lock().unwrap();
clash.tun_mode(tun_mode.unwrap())?; wrap_err!(clash.tun_mode(tun_mode.unwrap()))?;
clash.update_config(); clash.update_config();
profiles.activate(&clash)?; wrap_err!(profiles.activate(&clash))?;
} }
Ok(()) Ok(())
@ -290,14 +300,14 @@ pub fn kill_sidecars() {
/// open app config dir /// open app config dir
#[tauri::command] #[tauri::command]
pub fn open_app_dir() -> Result<(), String> { pub fn open_app_dir() -> Result<(), String> {
let app_dir = app_home_dir(); let app_dir = dirs::app_home_dir();
open_path_cmd(app_dir, "failed to open app dir") open_path_cmd(app_dir, "failed to open app dir")
} }
/// open logs dir /// open logs dir
#[tauri::command] #[tauri::command]
pub fn open_logs_dir() -> Result<(), String> { pub fn open_logs_dir() -> Result<(), String> {
let log_dir = app_home_dir().join("logs"); let log_dir = dirs::app_logs_dir();
open_path_cmd(log_dir, "failed to open logs dir") open_path_cmd(log_dir, "failed to open logs dir")
} }

View File

@ -1,5 +1,6 @@
use super::{Profiles, Verge}; use super::{Profiles, Verge};
use crate::utils::{config, dirs}; use crate::utils::{config, dirs};
use anyhow::{bail, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_yaml::{Mapping, Value}; use serde_yaml::{Mapping, Value};
use tauri::api::process::{Command, CommandChild, CommandEvent}; use tauri::api::process::{Command, CommandChild, CommandEvent};
@ -31,12 +32,9 @@ pub struct Clash {
pub sidecar: Option<CommandChild>, pub sidecar: Option<CommandChild>,
} }
static CLASH_CONFIG: &str = "config.yaml";
// todo: be able to change config field
impl Clash { impl Clash {
pub fn new() -> Clash { pub fn new() -> Clash {
let config = Clash::get_config(); let config = Clash::read_config();
let info = Clash::get_info(&config); let info = Clash::get_info(&config);
Clash { Clash {
@ -46,6 +44,20 @@ impl Clash {
} }
} }
/// get clash config
fn read_config() -> Mapping {
config::read_yaml::<Mapping>(dirs::clash_path())
}
/// save the clash config
fn save_config(&self) -> Result<()> {
config::save_yaml(
dirs::clash_path(),
&self.config,
Some("# Default Config For Clash Core\n\n"),
)
}
/// parse the clash's config.yaml /// parse the clash's config.yaml
/// get some information /// get some information
fn get_info(clash_config: &Mapping) -> ClashInfo { fn get_info(clash_config: &Mapping) -> ClashInfo {
@ -100,7 +112,7 @@ impl Clash {
} }
/// run clash sidecar /// run clash sidecar
pub fn run_sidecar(&mut self) -> Result<(), String> { pub fn run_sidecar(&mut self) -> Result<()> {
let app_dir = dirs::app_home_dir(); let app_dir = dirs::app_home_dir();
let app_dir = app_dir.as_os_str().to_str().unwrap(); let app_dir = app_dir.as_os_str().to_str().unwrap();
@ -121,25 +133,23 @@ impl Clash {
}); });
Ok(()) Ok(())
} }
Err(err) => Err(err.to_string()), Err(err) => bail!(err.to_string()),
}, },
Err(err) => Err(err.to_string()), Err(err) => bail!(err.to_string()),
} }
} }
/// drop clash sidecar /// drop clash sidecar
pub fn drop_sidecar(&mut self) -> Result<(), String> { pub fn drop_sidecar(&mut self) -> Result<()> {
if let Some(sidecar) = self.sidecar.take() { if let Some(sidecar) = self.sidecar.take() {
if let Err(err) = sidecar.kill() { sidecar.kill()?;
return Err(format!("failed to drop clash for \"{}\"", err));
}
} }
Ok(()) Ok(())
} }
/// restart clash sidecar /// restart clash sidecar
/// should reactivate profile after restart /// should reactivate profile after restart
pub fn restart_sidecar(&mut self, profiles: &mut Profiles) -> Result<(), String> { pub fn restart_sidecar(&mut self, profiles: &mut Profiles) -> Result<()> {
self.update_config(); self.update_config();
self.drop_sidecar()?; self.drop_sidecar()?;
self.run_sidecar()?; self.run_sidecar()?;
@ -148,31 +158,17 @@ impl Clash {
/// update the clash info /// update the clash info
pub fn update_config(&mut self) { pub fn update_config(&mut self) {
self.config = Clash::get_config(); self.config = Clash::read_config();
self.info = Clash::get_info(&self.config); self.info = Clash::get_info(&self.config);
} }
/// get clash config
fn get_config() -> Mapping {
config::read_yaml::<Mapping>(dirs::app_home_dir().join(CLASH_CONFIG))
}
/// save the clash config
fn save_config(&self) -> Result<(), String> {
config::save_yaml(
dirs::app_home_dir().join(CLASH_CONFIG),
&self.config,
Some("# Default Config For Clash Core\n\n"),
)
}
/// patch update the clash config /// patch update the clash config
pub fn patch_config( pub fn patch_config(
&mut self, &mut self,
patch: Mapping, patch: Mapping,
verge: &mut Verge, verge: &mut Verge,
profiles: &mut Profiles, profiles: &mut Profiles,
) -> Result<(), String> { ) -> Result<()> {
for (key, value) in patch.iter() { for (key, value) in patch.iter() {
let value = value.clone(); let value = value.clone();
let key_str = key.as_str().clone().unwrap_or(""); let key_str = key.as_str().clone().unwrap_or("");
@ -206,7 +202,7 @@ impl Clash {
/// enable tun mode /// enable tun mode
/// only revise the config and restart the /// only revise the config and restart the
pub fn tun_mode(&mut self, enable: bool) -> Result<(), String> { pub fn tun_mode(&mut self, enable: bool) -> Result<()> {
let tun_key = Value::String("tun".into()); let tun_key = Value::String("tun".into());
let tun_val = self.config.get(&tun_key); let tun_val = self.config.get(&tun_key);
@ -256,7 +252,7 @@ impl Default for Clash {
impl Drop for Clash { impl Drop for Clash {
fn drop(&mut self) { fn drop(&mut self) {
if let Err(err) = self.drop_sidecar() { if let Err(err) = self.drop_sidecar() {
log::error!("{}", err); log::error!("{err}");
} }
} }
} }

View File

@ -1,10 +1,10 @@
use super::{Clash, ClashInfo}; use super::{Clash, ClashInfo};
use crate::utils::{config, dirs, tmpl}; use crate::utils::{config, dirs, tmpl};
use anyhow::{bail, Result};
use reqwest::header::HeaderMap; use reqwest::header::HeaderMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_yaml::{Mapping, Value}; use serde_yaml::{Mapping, Value};
use std::collections::HashMap; use std::collections::HashMap;
use std::env::temp_dir;
use std::fs::{remove_file, File}; use std::fs::{remove_file, File};
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
@ -24,18 +24,30 @@ pub struct Profiles {
pub struct ProfileItem { pub struct ProfileItem {
/// profile name /// profile name
pub name: Option<String>, pub name: Option<String>,
/// profile description /// profile description
#[serde(skip_serializing_if = "Option::is_none")]
pub desc: Option<String>, pub desc: Option<String>,
/// profile file /// profile file
pub file: Option<String>, pub file: Option<String>,
/// current mode /// current mode
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<String>, pub mode: Option<String>,
/// source url /// source url
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>, pub url: Option<String>,
/// selected infomation /// selected infomation
#[serde(skip_serializing_if = "Option::is_none")]
pub selected: Option<Vec<ProfileSelected>>, pub selected: Option<Vec<ProfileSelected>>,
/// user info /// user info
#[serde(skip_serializing_if = "Option::is_none")]
pub extra: Option<ProfileExtra>, pub extra: Option<ProfileExtra>,
/// updated time /// updated time
pub updated: Option<usize>, pub updated: Option<usize>,
} }
@ -63,29 +75,26 @@ pub struct ProfileResponse {
pub extra: Option<ProfileExtra>, pub extra: Option<ProfileExtra>,
} }
static PROFILE_YAML: &str = "profiles.yaml";
static PROFILE_TEMP: &str = "clash-verge-runtime.yaml";
impl Profiles { impl Profiles {
/// read the config from the file /// read the config from the file
pub fn read_file() -> Self { pub fn read_file() -> Self {
config::read_yaml::<Profiles>(dirs::app_home_dir().join(PROFILE_YAML)) config::read_yaml::<Profiles>(dirs::profiles_path())
} }
/// save the config to the file /// save the config to the file
pub fn save_file(&self) -> Result<(), String> { pub fn save_file(&self) -> Result<()> {
config::save_yaml( config::save_yaml(
dirs::app_home_dir().join(PROFILE_YAML), dirs::profiles_path(),
self, self,
Some("# Profiles Config for Clash Verge\n\n"), Some("# Profiles Config for Clash Verge\n\n"),
) )
} }
/// sync the config between file and memory /// sync the config between file and memory
pub fn sync_file(&mut self) -> Result<(), String> { pub fn sync_file(&mut self) -> Result<()> {
let data = config::read_yaml::<Self>(dirs::app_home_dir().join(PROFILE_YAML)); let data = config::read_yaml::<Self>(dirs::profiles_path());
if data.current.is_none() { if data.current.is_none() {
Err("failed to read profiles.yaml".into()) bail!("failed to read profiles.yaml")
} else { } else {
self.current = data.current; self.current = data.current;
self.items = data.items; self.items = data.items;
@ -95,9 +104,9 @@ impl Profiles {
/// import the new profile from the url /// import the new profile from the url
/// and update the config file /// and update the config file
pub fn import_from_url(&mut self, url: String, result: ProfileResponse) -> Result<(), String> { pub fn import_from_url(&mut self, url: String, result: ProfileResponse) -> Result<()> {
// save the profile file // save the profile file
let path = dirs::app_home_dir().join("profiles").join(&result.file); let path = dirs::app_profiles_dir().join(&result.file);
let file_data = result.data.as_bytes(); let file_data = result.data.as_bytes();
File::create(path).unwrap().write(file_data).unwrap(); File::create(path).unwrap().write(file_data).unwrap();
@ -130,11 +139,11 @@ impl Profiles {
} }
/// set the current and save to file /// set the current and save to file
pub fn put_current(&mut self, index: usize) -> Result<(), String> { pub fn put_current(&mut self, index: usize) -> Result<()> {
let items = self.items.take().unwrap_or(vec![]); let items = self.items.take().unwrap_or(vec![]);
if index >= items.len() { if index >= items.len() {
return Err("the index out of bound".into()); bail!("the index out of bound");
} }
self.items = Some(items); self.items = Some(items);
@ -144,7 +153,7 @@ impl Profiles {
/// append new item /// append new item
/// return the new item's index /// return the new item's index
pub fn append_item(&mut self, name: String, desc: String) -> Result<(usize, PathBuf), String> { pub fn append_item(&mut self, name: String, desc: String) -> Result<(usize, PathBuf)> {
let mut items = self.items.take().unwrap_or(vec![]); let mut items = self.items.take().unwrap_or(vec![]);
// create a new profile file // create a new profile file
@ -153,7 +162,7 @@ impl Profiles {
.unwrap() .unwrap()
.as_secs(); .as_secs();
let file = format!("{}.yaml", now); let file = format!("{}.yaml", now);
let path = dirs::app_home_dir().join("profiles").join(&file); let path = dirs::app_profiles_dir().join(&file);
match File::create(&path).unwrap().write(tmpl::ITEM_CONFIG) { match File::create(&path).unwrap().write(tmpl::ITEM_CONFIG) {
Ok(_) => { Ok(_) => {
@ -172,14 +181,14 @@ impl Profiles {
self.items = Some(items); self.items = Some(items);
Ok((index, path)) Ok((index, path))
} }
Err(_) => Err("failed to create file".into()), Err(_) => bail!("failed to create file"),
} }
} }
/// update the target profile /// update the target profile
/// and save to config file /// and save to config file
/// only support the url item /// only support the url item
pub fn update_item(&mut self, index: usize, result: ProfileResponse) -> Result<(), String> { pub fn update_item(&mut self, index: usize, result: ProfileResponse) -> Result<()> {
let mut items = self.items.take().unwrap_or(vec![]); let mut items = self.items.take().unwrap_or(vec![]);
let now = SystemTime::now() let now = SystemTime::now()
@ -189,7 +198,7 @@ impl Profiles {
// update file // update file
let file_path = &items[index].file.as_ref().unwrap(); let file_path = &items[index].file.as_ref().unwrap();
let file_path = dirs::app_home_dir().join("profiles").join(file_path); let file_path = dirs::app_profiles_dir().join(file_path);
let file_data = result.data.as_bytes(); let file_data = result.data.as_bytes();
File::create(file_path).unwrap().write(file_data).unwrap(); File::create(file_path).unwrap().write(file_data).unwrap();
@ -202,10 +211,10 @@ impl Profiles {
} }
/// patch item /// patch item
pub fn patch_item(&mut self, index: usize, profile: ProfileItem) -> Result<(), String> { pub fn patch_item(&mut self, index: usize, profile: ProfileItem) -> Result<()> {
let mut items = self.items.take().unwrap_or(vec![]); let mut items = self.items.take().unwrap_or(vec![]);
if index >= items.len() { if index >= items.len() {
return Err("index out of bound".into()); bail!("index out of range");
} }
if profile.name.is_some() { if profile.name.is_some() {
@ -232,19 +241,19 @@ impl Profiles {
} }
/// delete the item /// delete the item
pub fn delete_item(&mut self, index: usize) -> Result<bool, String> { pub fn delete_item(&mut self, index: usize) -> Result<bool> {
let mut current = self.current.clone().unwrap_or(0); let mut current = self.current.clone().unwrap_or(0);
let mut items = self.items.clone().unwrap_or(vec![]); let mut items = self.items.clone().unwrap_or(vec![]);
if index >= items.len() { if index >= items.len() {
return Err("index out of bound".into()); bail!("index out of range");
} }
let mut rm_item = items.remove(index); let mut rm_item = items.remove(index);
// delete the file // delete the file
if let Some(file) = rm_item.file.take() { if let Some(file) = rm_item.file.take() {
let file_path = dirs::app_home_dir().join("profiles").join(file); let file_path = dirs::app_profiles_dir().join(file);
if file_path.exists() { if file_path.exists() {
if let Err(err) = remove_file(file_path) { if let Err(err) = remove_file(file_path) {
@ -272,33 +281,34 @@ impl Profiles {
} }
/// activate current profile /// activate current profile
pub fn activate(&self, clash: &Clash) -> Result<(), String> { pub fn activate(&self, clash: &Clash) -> Result<()> {
let current = self.current.unwrap_or(0); let current = self.current.unwrap_or(0);
match self.items.clone() { match self.items.clone() {
Some(items) => { Some(items) => {
if current >= items.len() { if current >= items.len() {
return Err("the index out of bound".into()); bail!("the index out of bound");
} }
let profile = items[current].clone(); let profile = items[current].clone();
let clash_config = clash.config.clone(); let clash_config = clash.config.clone();
let clash_info = clash.info.clone(); let clash_info = clash.info.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
let mut count = 5; // retry times let mut count = 5; // retry times
let mut err = String::from(""); let mut err = None;
while count > 0 { while count > 0 {
match activate_profile(&profile, &clash_config, &clash_info).await { match activate_profile(&profile, &clash_config, &clash_info).await {
Ok(_) => return, Ok(_) => return,
Err(e) => err = e, Err(e) => err = Some(e),
} }
count -= 1; count -= 1;
} }
log::error!("failed to activate for `{}`", err); log::error!("failed to activate for `{}`", err.unwrap());
}); });
Ok(()) Ok(())
} }
None => Err("empty profiles".into()), None => bail!("empty profiles"),
} }
} }
} }
@ -308,23 +318,23 @@ pub async fn activate_profile(
profile_item: &ProfileItem, profile_item: &ProfileItem,
clash_config: &Mapping, clash_config: &Mapping,
clash_info: &ClashInfo, clash_info: &ClashInfo,
) -> Result<(), String> { ) -> Result<()> {
// temp profile's path // temp profile's path
let temp_path = temp_dir().join(PROFILE_TEMP); let temp_path = dirs::profiles_temp_path();
// generate temp profile // generate temp profile
{ {
let file_name = match profile_item.file.clone() { let file_name = match profile_item.file.clone() {
Some(file_name) => file_name, Some(file_name) => file_name,
None => return Err("profile item should have `file` field".into()), None => bail!("profile item should have `file` field"),
}; };
let file_path = dirs::app_home_dir().join("profiles").join(file_name); let file_path = dirs::app_profiles_dir().join(file_name);
if !file_path.exists() { if !file_path.exists() {
return Err(format!( bail!(
"profile `{}` not exists", "profile `{}` not exists",
file_path.as_os_str().to_str().unwrap() file_path.as_os_str().to_str().unwrap()
)); );
} }
// begin to generate the new profile config // begin to generate the new profile config
@ -372,12 +382,13 @@ pub async fn activate_profile(
let mut data = HashMap::new(); let mut data = HashMap::new();
data.insert("path", temp_path.as_os_str().to_str().unwrap()); data.insert("path", temp_path.as_os_str().to_str().unwrap());
let client = match reqwest::ClientBuilder::new().no_proxy().build() { let client = reqwest::ClientBuilder::new().no_proxy().build()?;
Ok(c) => c,
Err(_) => return Err("failed to create http::put".into()), client
}; .put(server)
match client.put(server).headers(headers).json(&data).send().await { .headers(headers)
Ok(_) => Ok(()), .json(&data)
Err(err) => Err(format!("request failed `{}`", err.to_string())), .send()
} .await?;
Ok(())
} }

View File

@ -2,6 +2,7 @@ use crate::{
core::Clash, core::Clash,
utils::{config, dirs, sysopt::SysProxyConfig}, utils::{config, dirs, sysopt::SysProxyConfig},
}; };
use anyhow::{bail, Result};
use auto_launch::{AutoLaunch, AutoLaunchBuilder}; use auto_launch::{AutoLaunch, AutoLaunchBuilder};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
@ -39,17 +40,15 @@ pub struct VergeConfig {
pub proxy_guard_duration: Option<u64>, pub proxy_guard_duration: Option<u64>,
} }
static VERGE_CONFIG: &str = "verge.yaml";
impl VergeConfig { impl VergeConfig {
pub fn new() -> Self { pub fn new() -> Self {
config::read_yaml::<VergeConfig>(dirs::app_home_dir().join(VERGE_CONFIG)) config::read_yaml::<VergeConfig>(dirs::verge_path())
} }
/// Save Verge App Config /// Save Verge App Config
pub fn save_file(&self) -> Result<(), String> { pub fn save_file(&self) -> Result<()> {
config::save_yaml( config::save_yaml(
dirs::app_home_dir().join(VERGE_CONFIG), dirs::verge_path(),
self, self,
Some("# The Config for Clash Verge App\n\n"), Some("# The Config for Clash Verge App\n\n"),
) )
@ -140,30 +139,28 @@ impl Verge {
} }
/// sync the startup when run the app /// sync the startup when run the app
pub fn sync_launch(&self) -> Result<(), String> { pub fn sync_launch(&self) -> Result<()> {
let enable = self.config.enable_auto_launch.clone().unwrap_or(false); let enable = self.config.enable_auto_launch.clone().unwrap_or(false);
if !enable { if !enable {
return Ok(()); return Ok(());
} }
if self.auto_launch.is_none() { if self.auto_launch.is_none() {
return Err("should init the auto launch first".into()); bail!("should init the auto launch first");
} }
let auto_launch = self.auto_launch.clone().unwrap(); let auto_launch = self.auto_launch.clone().unwrap();
let is_enabled = auto_launch.is_enabled().unwrap_or(false); let is_enabled = auto_launch.is_enabled().unwrap_or(false);
if !is_enabled { if !is_enabled {
if let Err(_) = auto_launch.enable() { auto_launch.enable()?;
return Err("failed to enable auto-launch".into());
}
} }
Ok(()) Ok(())
} }
/// update the startup /// update the startup
fn update_launch(&mut self, enable: bool) -> Result<(), String> { fn update_launch(&mut self, enable: bool) -> Result<()> {
let conf_enable = self.config.enable_auto_launch.clone().unwrap_or(false); let conf_enable = self.config.enable_auto_launch.clone().unwrap_or(false);
if enable == conf_enable { if enable == conf_enable {
@ -172,24 +169,18 @@ impl Verge {
let auto_launch = self.auto_launch.clone().unwrap(); let auto_launch = self.auto_launch.clone().unwrap();
let result = match enable { match enable {
true => auto_launch.enable(), true => auto_launch.enable()?,
false => auto_launch.disable(), false => auto_launch.disable()?,
}; };
match result { Ok(())
Ok(_) => Ok(()),
Err(err) => {
log::error!("{err}");
Err("failed to set system startup info".into())
}
}
} }
/// patch verge config /// patch verge config
/// There should be only one update at a time here /// There should be only one update at a time here
/// so call the save_file at the end is savely /// so call the save_file at the end is savely
pub fn patch_config(&mut self, patch: VergeConfig) -> Result<(), String> { pub fn patch_config(&mut self, patch: VergeConfig) -> Result<()> {
// only change it // only change it
if patch.theme_mode.is_some() { if patch.theme_mode.is_some() {
self.config.theme_mode = patch.theme_mode; self.config.theme_mode = patch.theme_mode;
@ -218,7 +209,7 @@ impl Verge {
self.cur_sysproxy = Some(sysproxy); self.cur_sysproxy = Some(sysproxy);
log::error!("failed to set system proxy"); log::error!("failed to set system proxy");
return Err("failed to set system proxy".into()); bail!("failed to set system proxy");
} }
self.cur_sysproxy = Some(sysproxy); self.cur_sysproxy = Some(sysproxy);
} }
@ -237,7 +228,7 @@ impl Verge {
self.cur_sysproxy = Some(sysproxy); self.cur_sysproxy = Some(sysproxy);
log::error!("failed to set system proxy"); log::error!("failed to set system proxy");
return Err("failed to set system proxy".into()); bail!("failed to set system proxy");
} }
} }

View File

@ -1,3 +1,4 @@
use anyhow::{Context, Result};
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
@ -7,26 +8,16 @@ pub fn read_yaml<T: DeserializeOwned + Default>(path: PathBuf) -> T {
serde_yaml::from_str::<T>(&yaml_str).unwrap_or(T::default()) serde_yaml::from_str::<T>(&yaml_str).unwrap_or(T::default())
} }
/// - save the data to the file /// save the data to the file
/// - can set `prefix` string to add some comments /// can set `prefix` string to add some comments
pub fn save_yaml<T: Serialize>( pub fn save_yaml<T: Serialize>(path: PathBuf, data: &T, prefix: Option<&str>) -> Result<()> {
path: PathBuf, let data_str = serde_yaml::to_string(data)?;
data: &T,
prefix: Option<&str>,
) -> Result<(), String> {
match serde_yaml::to_string(data) {
Ok(data_str) => {
let yaml_str = match prefix {
Some(prefix) => format!("{}{}", prefix, data_str),
None => data_str,
};
let path_str = path.as_os_str().to_string_lossy().to_string(); let yaml_str = match prefix {
match fs::write(path, yaml_str.as_bytes()) { Some(prefix) => format!("{prefix}{data_str}"),
Ok(_) => Ok(()), None => data_str,
Err(_) => Err(format!("can not save file `{}`", path_str)), };
}
} let path_str = path.as_os_str().to_string_lossy().to_string();
Err(_) => Err("can not convert the data to yaml".into()), fs::write(path, yaml_str.as_bytes()).context(format!("failed to save file \"{path_str}\""))
}
} }

View File

@ -1,3 +1,4 @@
use std::env::temp_dir;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tauri::{ use tauri::{
api::path::{home_dir, resource_dir}, api::path::{home_dir, resource_dir},
@ -18,3 +19,34 @@ pub fn app_resources_dir(package_info: &PackageInfo) -> PathBuf {
.unwrap() .unwrap()
.join("resources") .join("resources")
} }
/// profiles dir
pub fn app_profiles_dir() -> PathBuf {
app_home_dir().join("profiles")
}
/// logs dir
pub fn app_logs_dir() -> PathBuf {
app_home_dir().join("logs")
}
static CLASH_CONFIG: &str = "config.yaml";
static VERGE_CONFIG: &str = "verge.yaml";
static PROFILE_YAML: &str = "profiles.yaml";
static PROFILE_TEMP: &str = "clash-verge-runtime.yaml";
pub fn clash_path() -> PathBuf {
app_home_dir().join(CLASH_CONFIG)
}
pub fn verge_path() -> PathBuf {
app_home_dir().join(VERGE_CONFIG)
}
pub fn profiles_path() -> PathBuf {
app_home_dir().join(PROFILE_YAML)
}
pub fn profiles_temp_path() -> PathBuf {
temp_dir().join(PROFILE_TEMP)
}

View File

@ -1,5 +1,3 @@
extern crate serde_yaml;
use crate::utils::{dirs, tmpl}; use crate::utils::{dirs, tmpl};
use chrono::Local; use chrono::Local;
use log::LevelFilter; use log::LevelFilter;
@ -7,7 +5,7 @@ use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender; use log4rs::append::file::FileAppender;
use log4rs::config::{Appender, Config, Root}; use log4rs::config::{Appender, Config, Root};
use log4rs::encode::pattern::PatternEncoder; use log4rs::encode::pattern::PatternEncoder;
use std::fs::{self, File}; use std::fs;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
use tauri::PackageInfo; use tauri::PackageInfo;
@ -48,13 +46,13 @@ fn init_config(app_dir: &PathBuf) -> std::io::Result<()> {
let profile_path = app_dir.join("profiles.yaml"); let profile_path = app_dir.join("profiles.yaml");
if !clash_path.exists() { if !clash_path.exists() {
File::create(clash_path)?.write(tmpl::CLASH_CONFIG)?; fs::File::create(clash_path)?.write(tmpl::CLASH_CONFIG)?;
} }
if !verge_path.exists() { if !verge_path.exists() {
File::create(verge_path)?.write(tmpl::VERGE_CONFIG)?; fs::File::create(verge_path)?.write(tmpl::VERGE_CONFIG)?;
} }
if !profile_path.exists() { if !profile_path.exists() {
File::create(profile_path)?.write(tmpl::PROFILES_CONFIG)?; fs::File::create(profile_path)?.write(tmpl::PROFILES_CONFIG)?;
} }
Ok(()) Ok(())
} }
@ -63,8 +61,8 @@ fn init_config(app_dir: &PathBuf) -> std::io::Result<()> {
pub fn init_app(package_info: &PackageInfo) { pub fn init_app(package_info: &PackageInfo) {
// create app dir // create app dir
let app_dir = dirs::app_home_dir(); let app_dir = dirs::app_home_dir();
let log_dir = app_dir.join("logs"); let log_dir = dirs::app_logs_dir();
let profiles_dir = app_dir.join("profiles"); let profiles_dir = dirs::app_profiles_dir();
let res_dir = dirs::app_resources_dir(package_info); let res_dir = dirs::app_resources_dir(package_info);