From b8ad328cdea286013df532ac358a4034ea03f5aa Mon Sep 17 00:00:00 2001
From: GyDi <segydi@foxmail.com>
Date: Wed, 20 Apr 2022 01:44:47 +0800
Subject: [PATCH] refactor: wip

---
 src-tauri/src/cmds.rs                   | 138 ++-----
 src-tauri/src/core/clash copy.rs        | 520 ------------------------
 src-tauri/src/core/mod.rs               | 108 ++++-
 src-tauri/src/core/prfitem.rs           |   2 +-
 src-tauri/src/core/profiles.rs          |  32 +-
 src-tauri/src/core/service.rs           |   2 +
 src-tauri/src/core/sysopt.rs            | 207 ++++++++++
 src-tauri/src/core/verge.rs             | 263 +-----------
 src-tauri/src/main.rs                   |  35 +-
 src-tauri/src/utils/help.rs             |  35 ++
 src-tauri/src/utils/resolve.rs          |   4 +-
 src/components/layout/update-dialog.tsx |   4 +-
 src/services/cmds.ts                    |   8 +-
 13 files changed, 419 insertions(+), 939 deletions(-)
 delete mode 100644 src-tauri/src/core/clash copy.rs
 create mode 100644 src-tauri/src/core/sysopt.rs

diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs
index 4d1e7a8..0b6604d 100644
--- a/src-tauri/src/cmds.rs
+++ b/src-tauri/src/cmds.rs
@@ -1,12 +1,11 @@
 use crate::{
   core::{ClashInfo, Core, PrfItem, PrfOption, Profiles, VergeConfig},
-  utils::{dirs, sysopt::SysProxyConfig},
+  utils::{dirs, help, sysopt::SysProxyConfig},
 };
 use crate::{log_if_err, ret_err, wrap_err};
 use anyhow::Result;
 use serde_yaml::Mapping;
-use std::process::Command;
-use tauri::{api, Manager, State};
+use tauri::{api, State};
 
 type CmdResult<T = ()> = Result<T, String>;
 
@@ -17,11 +16,10 @@ pub fn get_profiles(core: State<'_, Core>) -> CmdResult<Profiles> {
   Ok(profiles.clone())
 }
 
-/// synchronize data irregularly
+/// manually exec enhanced profile
 #[tauri::command]
-pub fn sync_profiles(core: State<'_, Core>) -> CmdResult {
-  let mut profiles = core.profiles.lock();
-  wrap_err!(profiles.sync_file())
+pub fn enhance_profiles(core: State<'_, Core>) -> CmdResult {
+  wrap_err!(core.activate_enhanced(false))
 }
 
 /// import the profile from url
@@ -102,23 +100,18 @@ pub fn select_profile(index: String, core: State<'_, Core>) -> CmdResult {
 
   drop(profiles);
 
-  log_if_err!(core.activate_enhanced(false));
-
-  Ok(())
+  wrap_err!(core.activate_enhanced(false))
 }
 
 /// change the profile chain
 #[tauri::command]
 pub fn change_profile_chain(chain: Option<Vec<String>>, core: State<'_, Core>) -> CmdResult {
-  dbg!("change profile chain");
   let mut profiles = core.profiles.lock();
   profiles.put_chain(chain);
-  dbg!("change profile chain finish");
+
   drop(profiles);
 
-  log_if_err!(core.activate_enhanced(false));
-
-  Ok(())
+  wrap_err!(core.activate_enhanced(false))
 }
 
 /// change the profile valid fields
@@ -126,18 +119,10 @@ pub fn change_profile_chain(chain: Option<Vec<String>>, core: State<'_, Core>) -
 pub fn change_profile_valid(valid: Option<Vec<String>>, core: State<Core>) -> CmdResult {
   let mut profiles = core.profiles.lock();
   profiles.put_valid(valid);
+
   drop(profiles);
 
-  log_if_err!(core.activate_enhanced(false));
-
-  Ok(())
-}
-
-/// manually exec enhanced profile
-#[tauri::command]
-pub fn enhance_profiles(core: State<'_, Core>) -> CmdResult {
-  log_if_err!(core.activate_enhanced(false));
-  Ok(())
+  wrap_err!(core.activate_enhanced(false))
 }
 
 /// delete profile item
@@ -147,7 +132,7 @@ pub fn delete_profile(index: String, core: State<'_, Core>) -> CmdResult {
 
   if wrap_err!(profiles.delete_item(index))? {
     drop(profiles);
-    // std::mem::drop(profiles);
+
     log_if_err!(core.activate_enhanced(false));
   }
 
@@ -178,32 +163,7 @@ pub fn view_profile(index: String, core: State<'_, Core>) -> CmdResult {
     ret_err!("the file not found");
   }
 
-  // use vscode first
-  if let Ok(code) = which::which("code") {
-    #[cfg(target_os = "windows")]
-    {
-      use std::os::windows::process::CommandExt;
-
-      if let Err(err) = Command::new(code)
-        .creation_flags(0x08000000)
-        .arg(path)
-        .spawn()
-      {
-        log::error!("failed to open file by VScode for {err}");
-        return Err("failed to open file by VScode".into());
-      }
-    }
-
-    #[cfg(not(target_os = "windows"))]
-    if let Err(err) = Command::new(code).arg(path).spawn() {
-      log::error!("failed to open file by VScode for {err}");
-      return Err("failed to open file by VScode".into());
-    }
-
-    return Ok(());
-  }
-
-  wrap_err!(open::that(path))
+  wrap_err!(help::open_file(path))
 }
 
 /// read the profile item file data
@@ -233,12 +193,6 @@ pub fn save_profile_file(
   wrap_err!(item.save_file(file_data.unwrap()))
 }
 
-/// restart the sidecar
-#[tauri::command]
-pub fn restart_sidecar(core: State<'_, Core>) -> CmdResult {
-  wrap_err!(core.restart_clash())
-}
-
 /// get the clash core info from the state
 /// the caller can also get the infomation by clash's api
 #[tauri::command]
@@ -255,29 +209,11 @@ pub fn patch_clash_config(payload: Mapping, core: State<'_, Core>) -> CmdResult
   wrap_err!(core.patch_clash(payload))
 }
 
-/// get the system proxy
-#[tauri::command]
-pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
-  wrap_err!(SysProxyConfig::get_sys())
-}
-
-/// get the current proxy config
-/// which may not the same as system proxy
-#[tauri::command]
-pub fn get_cur_proxy(core: State<'_, Core>) -> CmdResult<Option<SysProxyConfig>> {
-  let verge = core.verge.lock();
-  Ok(verge.cur_sysproxy.clone())
-}
-
 /// get the verge config
 #[tauri::command]
 pub fn get_verge_config(core: State<'_, Core>) -> CmdResult<VergeConfig> {
   let verge = core.verge.lock();
-  let mut config = verge.config.clone();
-
-  if config.system_proxy_bypass.is_none() && verge.cur_sysproxy.is_some() {
-    config.system_proxy_bypass = Some(verge.cur_sysproxy.clone().unwrap().bypass)
-  }
+  let config = verge.config.clone();
 
   Ok(config)
 }
@@ -290,41 +226,35 @@ pub fn patch_verge_config(
   app_handle: tauri::AppHandle,
   core: State<'_, Core>,
 ) -> Result<(), String> {
-  let tun_mode = payload.enable_tun_mode.clone();
-  let system_proxy = payload.enable_system_proxy.clone();
+  wrap_err!(core.patch_verge(payload, &app_handle))
+}
 
-  let mut verge = core.verge.lock();
-  wrap_err!(verge.patch_config(payload))?;
-
-  // change system tray
-  if system_proxy.is_some() || tun_mode.is_some() {
-    verge.update_systray(&app_handle).unwrap();
-  }
-
-  // change tun mode
-  if tun_mode.is_some() {
-    #[cfg(target_os = "windows")]
-    if *tun_mode.as_ref().unwrap() {
-      let wintun_dll = dirs::app_home_dir().join("wintun.dll");
-      if !wintun_dll.exists() {
-        log::error!("failed to enable TUN for missing `wintun.dll`");
-        return Err("failed to enable TUN for missing `wintun.dll`".into());
-      }
-    }
-
-    std::mem::drop(verge);
-    log_if_err!(core.activate_enhanced(false));
-  }
-
-  Ok(())
+/// restart the sidecar
+#[tauri::command]
+pub fn restart_sidecar(core: State<'_, Core>) -> CmdResult {
+  wrap_err!(core.restart_clash())
 }
 
 /// kill all sidecars when update app
 #[tauri::command]
-pub fn kill_sidecars() {
+pub fn kill_sidecar() {
   api::process::kill_children();
 }
 
+/// get the system proxy
+#[tauri::command]
+pub fn get_sys_proxy() -> Result<SysProxyConfig, String> {
+  wrap_err!(SysProxyConfig::get_sys())
+}
+
+/// get the current proxy config
+/// which may not the same as system proxy
+#[tauri::command]
+pub fn get_cur_proxy(core: State<'_, Core>) -> CmdResult<Option<SysProxyConfig>> {
+  let sysopt = core.sysopt.lock();
+  wrap_err!(sysopt.get_sysproxy())
+}
+
 /// open app config dir
 #[tauri::command]
 pub fn open_app_dir() -> Result<(), String> {
diff --git a/src-tauri/src/core/clash copy.rs b/src-tauri/src/core/clash copy.rs
deleted file mode 100644
index 463ab14..0000000
--- a/src-tauri/src/core/clash copy.rs	
+++ /dev/null
@@ -1,520 +0,0 @@
-use super::{PrfEnhancedResult, Profiles, Verge, VergeConfig};
-use crate::log_if_err;
-use crate::utils::{config, dirs, help};
-use anyhow::{bail, Result};
-use reqwest::header::HeaderMap;
-use serde::{Deserialize, Serialize};
-use serde_yaml::{Mapping, Value};
-use std::{collections::HashMap, time::Duration};
-use tauri::api::process::{Command, CommandChild, CommandEvent};
-use tauri::Window;
-use tokio::time::sleep;
-
-#[derive(Default, Debug, Clone, Deserialize, Serialize)]
-pub struct ClashInfo {
-  /// clash sidecar status
-  pub status: String,
-
-  /// clash core port
-  pub port: Option<String>,
-
-  /// same as `external-controller`
-  pub server: Option<String>,
-
-  /// clash secret
-  pub secret: Option<String>,
-}
-
-pub struct Clash {
-  /// maintain the clash config
-  pub config: Mapping,
-
-  /// some info
-  pub info: ClashInfo,
-
-  /// clash sidecar
-  pub sidecar: Option<CommandChild>,
-
-  /// save the main window
-  pub window: Option<Window>,
-}
-
-impl Clash {
-  pub fn new() -> Clash {
-    let config = Clash::read_config();
-    let info = Clash::get_info(&config);
-
-    Clash {
-      config,
-      info,
-      sidecar: None,
-      window: None,
-    }
-  }
-
-  /// 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
-  /// get some information
-  fn get_info(clash_config: &Mapping) -> ClashInfo {
-    let key_port_1 = Value::from("port");
-    let key_port_2 = Value::from("mixed-port");
-    let key_server = Value::from("external-controller");
-    let key_secret = Value::from("secret");
-
-    let port = match clash_config.get(&key_port_1) {
-      Some(value) => match value {
-        Value::String(val_str) => Some(val_str.clone()),
-        Value::Number(val_num) => Some(val_num.to_string()),
-        _ => None,
-      },
-      _ => None,
-    };
-    let port = match port {
-      Some(_) => port,
-      None => match clash_config.get(&key_port_2) {
-        Some(value) => match value {
-          Value::String(val_str) => Some(val_str.clone()),
-          Value::Number(val_num) => Some(val_num.to_string()),
-          _ => None,
-        },
-        _ => None,
-      },
-    };
-
-    let server = match clash_config.get(&key_server) {
-      Some(value) => match value {
-        Value::String(val_str) => {
-          // `external-controller` could be
-          // "127.0.0.1:9090" or ":9090"
-          // Todo: maybe it could support single port
-          let server = val_str.clone();
-          let server = match server.starts_with(":") {
-            true => format!("127.0.0.1{server}"),
-            false => server,
-          };
-
-          Some(server)
-        }
-        _ => None,
-      },
-      _ => None,
-    };
-    let secret = match clash_config.get(&key_secret) {
-      Some(value) => match value {
-        Value::String(val_str) => Some(val_str.clone()),
-        Value::Bool(val_bool) => Some(val_bool.to_string()),
-        Value::Number(val_num) => Some(val_num.to_string()),
-        _ => None,
-      },
-      _ => None,
-    };
-
-    ClashInfo {
-      status: "init".into(),
-      port,
-      server,
-      secret,
-    }
-  }
-
-  /// save the main window
-  pub fn set_window(&mut self, win: Option<Window>) {
-    self.window = win;
-  }
-
-  /// run clash sidecar
-  pub fn run_sidecar(&mut self, profiles: &Profiles, delay: bool) -> Result<()> {
-    let app_dir = dirs::app_home_dir();
-    let app_dir = app_dir.as_os_str().to_str().unwrap();
-
-    let cmd = Command::new_sidecar("clash")?;
-    let (mut rx, cmd_child) = cmd.args(["-d", app_dir]).spawn()?;
-
-    self.sidecar = Some(cmd_child);
-
-    // clash log
-    tauri::async_runtime::spawn(async move {
-      while let Some(event) = rx.recv().await {
-        match event {
-          CommandEvent::Stdout(line) => log::info!("[clash]: {}", line),
-          CommandEvent::Stderr(err) => log::error!("[clash]: {}", err),
-          _ => {}
-        }
-      }
-    });
-
-    // activate profile
-    log_if_err!(self.activate(&profiles));
-    log_if_err!(self.activate_enhanced(&profiles, delay, true));
-
-    Ok(())
-  }
-
-  /// drop clash sidecar
-  pub fn drop_sidecar(&mut self) -> Result<()> {
-    if let Some(sidecar) = self.sidecar.take() {
-      sidecar.kill()?;
-    }
-    Ok(())
-  }
-
-  /// restart clash sidecar
-  /// should reactivate profile after restart
-  pub fn restart_sidecar(&mut self, profiles: &mut Profiles) -> Result<()> {
-    self.update_config();
-    self.drop_sidecar()?;
-    self.run_sidecar(profiles, false)
-  }
-
-  /// update the clash info
-  pub fn update_config(&mut self) {
-    self.config = Clash::read_config();
-    self.info = Clash::get_info(&self.config);
-  }
-
-  /// patch update the clash config
-  pub fn patch_config(
-    &mut self,
-    patch: Mapping,
-    verge: &mut Verge,
-    profiles: &mut Profiles,
-  ) -> Result<()> {
-    let mix_port_key = Value::from("mixed-port");
-    let mut port = None;
-
-    for (key, value) in patch.into_iter() {
-      let value = value.clone();
-
-      // check whether the mix_port is changed
-      if key == mix_port_key {
-        if value.is_number() {
-          port = value.as_i64().as_ref().map(|n| n.to_string());
-        } else {
-          port = value.as_str().as_ref().map(|s| s.to_string());
-        }
-      }
-
-      self.config.insert(key.clone(), value);
-    }
-
-    self.save_config()?;
-
-    if let Some(port) = port {
-      self.restart_sidecar(profiles)?;
-      verge.init_sysproxy(Some(port));
-    }
-
-    Ok(())
-  }
-
-  /// revise the `tun` and `dns` config
-  fn _tun_mode(mut config: Mapping, enable: bool) -> Mapping {
-    macro_rules! revise {
-      ($map: expr, $key: expr, $val: expr) => {
-        let ret_key = Value::String($key.into());
-        $map.insert(ret_key, Value::from($val));
-      };
-    }
-
-    // if key not exists then append value
-    macro_rules! append {
-      ($map: expr, $key: expr, $val: expr) => {
-        let ret_key = Value::String($key.into());
-        if !$map.contains_key(&ret_key) {
-          $map.insert(ret_key, Value::from($val));
-        }
-      };
-    }
-
-    // tun config
-    let tun_val = config.get(&Value::from("tun"));
-    let mut new_tun = Mapping::new();
-
-    if tun_val.is_some() && tun_val.as_ref().unwrap().is_mapping() {
-      new_tun = tun_val.as_ref().unwrap().as_mapping().unwrap().clone();
-    }
-
-    revise!(new_tun, "enable", enable);
-
-    if enable {
-      append!(new_tun, "stack", "gvisor");
-      append!(new_tun, "dns-hijack", vec!["198.18.0.2:53"]);
-      append!(new_tun, "auto-route", true);
-      append!(new_tun, "auto-detect-interface", true);
-    }
-
-    revise!(config, "tun", new_tun);
-
-    // dns config
-    let dns_val = config.get(&Value::from("dns"));
-    let mut new_dns = Mapping::new();
-
-    if dns_val.is_some() && dns_val.as_ref().unwrap().is_mapping() {
-      new_dns = dns_val.as_ref().unwrap().as_mapping().unwrap().clone();
-    }
-
-    // 借鉴cfw的默认配置
-    revise!(new_dns, "enable", enable);
-
-    if enable {
-      append!(new_dns, "enhanced-mode", "fake-ip");
-      append!(
-        new_dns,
-        "nameserver",
-        vec!["114.114.114.114", "223.5.5.5", "8.8.8.8"]
-      );
-      append!(new_dns, "fallback", vec![] as Vec<&str>);
-
-      #[cfg(target_os = "windows")]
-      append!(
-        new_dns,
-        "fake-ip-filter",
-        vec![
-          "dns.msftncsi.com",
-          "www.msftncsi.com",
-          "www.msftconnecttest.com"
-        ]
-      );
-    }
-
-    revise!(config, "dns", new_dns);
-    config
-  }
-
-  /// activate the profile
-  /// generate a new profile to the temp_dir
-  /// then put the path to the clash core
-  fn _activate(info: ClashInfo, config: Mapping, window: Option<Window>) -> Result<()> {
-    let verge_config = VergeConfig::new();
-    let tun_enable = verge_config.enable_tun_mode.unwrap_or(false);
-
-    let config = Clash::_tun_mode(config, tun_enable);
-
-    let temp_path = dirs::profiles_temp_path();
-    config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
-
-    tauri::async_runtime::spawn(async move {
-      if info.server.is_none() {
-        return;
-      }
-
-      let server = info.server.unwrap();
-      let server = format!("http://{server}/configs");
-
-      let mut headers = HeaderMap::new();
-      headers.insert("Content-Type", "application/json".parse().unwrap());
-
-      if let Some(secret) = info.secret.as_ref() {
-        let secret = format!("Bearer {}", secret.clone()).parse().unwrap();
-        headers.insert("Authorization", secret);
-      }
-
-      let mut data = HashMap::new();
-      data.insert("path", temp_path.as_os_str().to_str().unwrap());
-
-      // retry 5 times
-      for _ in 0..5 {
-        match reqwest::ClientBuilder::new().no_proxy().build() {
-          Ok(client) => {
-            let builder = client.put(&server).headers(headers.clone()).json(&data);
-
-            match builder.send().await {
-              Ok(resp) => {
-                if resp.status() != 204 {
-                  log::error!("failed to activate clash for status \"{}\"", resp.status());
-                }
-
-                // emit the window to update something
-                if let Some(window) = window {
-                  window.emit("verge://refresh-clash-config", "yes").unwrap();
-                }
-
-                // do not retry
-                break;
-              }
-              Err(err) => log::error!("failed to activate for `{err}`"),
-            }
-          }
-          Err(err) => log::error!("failed to activate for `{err}`"),
-        }
-        sleep(Duration::from_millis(500)).await;
-      }
-    });
-
-    Ok(())
-  }
-
-  /// enhanced profiles mode
-  /// - (sync) refresh config if enhance chain is null
-  /// - (async) enhanced config
-  pub fn activate_enhanced(&self, profiles: &Profiles, delay: bool, skip: bool) -> Result<()> {
-    if self.window.is_none() {
-      bail!("failed to get the main window");
-    }
-
-    let event_name = help::get_uid("e");
-    let event_name = format!("enhanced-cb-{event_name}");
-
-    // generate the payload
-    let payload = profiles.gen_enhanced(event_name.clone())?;
-
-    let info = self.info.clone();
-
-    // do not run enhanced
-    if payload.chain.len() == 0 {
-      if skip {
-        return Ok(());
-      }
-
-      let mut config = self.config.clone();
-      let filter_data = Clash::strict_filter(payload.current);
-
-      for (key, value) in filter_data.into_iter() {
-        config.insert(key, value);
-      }
-
-      return Clash::_activate(info, config, self.window.clone());
-    }
-
-    let window = self.window.clone().unwrap();
-    let window_move = self.window.clone();
-
-    window.once(&event_name, move |event| {
-      if let Some(result) = event.payload() {
-        let result: PrfEnhancedResult = serde_json::from_str(result).unwrap();
-
-        if let Some(data) = result.data {
-          let mut config = Clash::read_config();
-          let filter_data = Clash::loose_filter(data); // loose filter
-
-          for (key, value) in filter_data.into_iter() {
-            config.insert(key, value);
-          }
-
-          log_if_err!(Clash::_activate(info, config, window_move));
-          log::info!("profile enhanced status {}", result.status);
-        }
-
-        result.error.map(|err| log::error!("{err}"));
-      }
-    });
-
-    tauri::async_runtime::spawn(async move {
-      // wait the window setup during resolve app
-      if delay {
-        sleep(Duration::from_secs(2)).await;
-      }
-      window.emit("script-handler", payload).unwrap();
-    });
-
-    Ok(())
-  }
-
-  /// activate the profile
-  /// auto activate enhanced profile
-  pub fn activate(&self, profiles: &Profiles) -> Result<()> {
-    let data = profiles.gen_activate()?;
-    let data = Clash::strict_filter(data);
-
-    let info = self.info.clone();
-    let mut config = self.config.clone();
-
-    for (key, value) in data.into_iter() {
-      config.insert(key, value);
-    }
-
-    Clash::_activate(info, config, self.window.clone())
-  }
-
-  /// only 5 default fields available (clash config fields)
-  /// convert to lowercase
-  fn strict_filter(config: Mapping) -> Mapping {
-    // Only the following fields are allowed:
-    // proxies/proxy-providers/proxy-groups/rule-providers/rules
-    let valid_keys = vec![
-      "proxies",
-      "proxy-providers",
-      "proxy-groups",
-      "rules",
-      "rule-providers",
-    ];
-
-    let mut new_config = Mapping::new();
-
-    for (key, value) in config.into_iter() {
-      key.as_str().map(|key_str| {
-        // change to lowercase
-        let mut key_str = String::from(key_str);
-        key_str.make_ascii_lowercase();
-
-        // filter
-        if valid_keys.contains(&&*key_str) {
-          new_config.insert(Value::String(key_str), value);
-        }
-      });
-    }
-
-    new_config
-  }
-
-  /// more clash config fields available
-  /// convert to lowercase
-  fn loose_filter(config: Mapping) -> Mapping {
-    // all of these can not be revised by script or merge
-    // http/https/socks port should be under control
-    let not_allow = vec![
-      "port",
-      "socks-port",
-      "mixed-port",
-      "allow-lan",
-      "mode",
-      "external-controller",
-      "secret",
-      "log-level",
-    ];
-
-    let mut new_config = Mapping::new();
-
-    for (key, value) in config.into_iter() {
-      key.as_str().map(|key_str| {
-        // change to lowercase
-        let mut key_str = String::from(key_str);
-        key_str.make_ascii_lowercase();
-
-        // filter
-        if !not_allow.contains(&&*key_str) {
-          new_config.insert(Value::String(key_str), value);
-        }
-      });
-    }
-
-    new_config
-  }
-}
-
-impl Default for Clash {
-  fn default() -> Self {
-    Clash::new()
-  }
-}
-
-impl Drop for Clash {
-  fn drop(&mut self) {
-    if let Err(err) = self.drop_sidecar() {
-      log::error!("{err}");
-    }
-  }
-}
diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs
index fb50efe..c15179a 100644
--- a/src-tauri/src/core/mod.rs
+++ b/src-tauri/src/core/mod.rs
@@ -1,14 +1,15 @@
 use self::notice::Notice;
 use self::service::Service;
+use self::sysopt::Sysopt;
 use crate::core::enhance::PrfEnhancedResult;
 use crate::log_if_err;
-use crate::utils::help;
+use crate::utils::{dirs, help};
 use anyhow::{bail, Result};
 use parking_lot::Mutex;
 use serde_yaml::Mapping;
 use std::sync::Arc;
 use std::time::Duration;
-use tauri::Window;
+use tauri::{AppHandle, Manager, Window};
 use tokio::time::sleep;
 
 mod clash;
@@ -17,6 +18,7 @@ mod notice;
 mod prfitem;
 mod profiles;
 mod service;
+mod sysopt;
 mod timer;
 mod verge;
 
@@ -35,6 +37,8 @@ pub struct Core {
 
   pub service: Arc<Mutex<Service>>,
 
+  pub sysopt: Arc<Mutex<Sysopt>>,
+
   pub window: Arc<Mutex<Option<Window>>>,
 }
 
@@ -50,10 +54,12 @@ impl Core {
       verge: Arc::new(Mutex::new(verge)),
       profiles: Arc::new(Mutex::new(profiles)),
       service: Arc::new(Mutex::new(service)),
+      sysopt: Arc::new(Mutex::new(Sysopt::new())),
       window: Arc::new(Mutex::new(None)),
     }
   }
 
+  /// initialize the core state
   pub fn init(&self, app_handle: tauri::AppHandle) {
     let mut service = self.service.lock();
     log_if_err!(service.start());
@@ -62,26 +68,30 @@ impl Core {
     log_if_err!(self.activate());
 
     let clash = self.clash.lock();
-    let mut verge = self.verge.lock();
+    let verge = self.verge.lock();
 
-    let hide = verge.config.enable_silent_start.clone().unwrap_or(false);
+    let silent_start = verge.config.enable_silent_start.clone();
+    let auto_launch = verge.config.enable_auto_launch.clone();
 
     // silent start
-    if hide {
+    if silent_start.unwrap_or(false) {
       let window = self.window.lock();
       window.as_ref().map(|win| {
         win.hide().unwrap();
       });
     }
 
-    verge.init_sysproxy(clash.info.port.clone());
+    let mut sysopt = self.sysopt.lock();
 
-    log_if_err!(verge.init_launch());
-    log_if_err!(verge.update_systray(&app_handle));
+    sysopt.init_sysproxy(clash.info.port.clone(), &verge);
 
     drop(clash);
     drop(verge);
 
+    log_if_err!(sysopt.init_launch(auto_launch));
+
+    log_if_err!(self.update_systray(&app_handle));
+
     // wait the window setup during resolve app
     let core = self.clone();
     tauri::async_runtime::spawn(async move {
@@ -106,6 +116,7 @@ impl Core {
     self.activate_enhanced(true)
   }
 
+  /// Patch Clash
   /// handle the clash config changed
   pub fn patch_clash(&self, patch: Mapping) -> Result<()> {
     let (changed, port) = {
@@ -123,13 +134,85 @@ impl Core {
       self.activate()?;
       self.activate_enhanced(true)?;
 
-      let mut verge = self.verge.lock();
-      verge.init_sysproxy(port);
+      let mut sysopt = self.sysopt.lock();
+      let verge = self.verge.lock();
+      sysopt.init_sysproxy(port, &verge);
     }
 
     Ok(())
   }
 
+  /// Patch Verge
+  pub fn patch_verge(&self, patch: VergeConfig, app_handle: &AppHandle) -> Result<()> {
+    let tun_mode = patch.enable_tun_mode.clone();
+    let auto_launch = patch.enable_auto_launch.clone();
+    let system_proxy = patch.enable_system_proxy.clone();
+    let proxy_bypass = patch.system_proxy_bypass.clone();
+    let proxy_guard = patch.enable_proxy_guard.clone();
+
+    if auto_launch.is_some() {
+      let mut sysopt = self.sysopt.lock();
+      sysopt.update_launch(auto_launch)?;
+    }
+
+    if system_proxy.is_some() || proxy_bypass.is_some() {
+      let mut sysopt = self.sysopt.lock();
+      sysopt.update_sysproxy(system_proxy.clone(), proxy_bypass)?;
+      sysopt.guard_proxy();
+    }
+
+    if proxy_guard.unwrap_or(false) {
+      let sysopt = self.sysopt.lock();
+      sysopt.guard_proxy();
+    }
+
+    #[cfg(target_os = "windows")]
+    if tun_mode.is_some() && *tun_mode.as_ref().unwrap_or(&false) {
+      let wintun_dll = dirs::app_home_dir().join("wintun.dll");
+      if !wintun_dll.exists() {
+        bail!("failed to enable TUN for missing `wintun.dll`");
+      }
+    }
+
+    // save the patch
+    let mut verge = self.verge.lock();
+    verge.patch_config(patch)?;
+    drop(verge);
+
+    if system_proxy.is_some() || tun_mode.is_some() {
+      self.update_systray(app_handle)?;
+    }
+
+    if tun_mode.is_some() {
+      self.activate_enhanced(false)?;
+    }
+
+    Ok(())
+  }
+
+  /// update the system tray state
+  pub fn update_systray(&self, app_handle: &AppHandle) -> Result<()> {
+    let verge = self.verge.lock();
+    let tray = app_handle.tray_handle();
+
+    let system_proxy = verge.config.enable_system_proxy.as_ref();
+    let tun_mode = verge.config.enable_tun_mode.as_ref();
+
+    tray
+      .get_item("system_proxy")
+      .set_selected(*system_proxy.unwrap_or(&false))?;
+    tray
+      .get_item("tun_mode")
+      .set_selected(*tun_mode.unwrap_or(&false))?;
+
+    // update verge config
+    let window = app_handle.get_window("main");
+    let notice = Notice::from(window);
+    notice.refresh_verge();
+
+    Ok(())
+  }
+
   /// activate the profile
   /// auto activate enhanced profile
   pub fn activate(&self) -> Result<()> {
@@ -165,6 +248,7 @@ impl Core {
     service.set_config(info, config, notice)
   }
 
+  /// Enhanced
   /// enhanced profiles mode
   pub fn activate_enhanced(&self, skip: bool) -> Result<()> {
     let window = self.window.lock();
@@ -235,10 +319,6 @@ impl Core {
       result.error.map(|err| log::error!("{err}"));
     });
 
-    // if delay {
-    //   sleep(Duration::from_secs(2)).await;
-    // }
-
     window.emit("script-handler", payload).unwrap();
 
     Ok(())
diff --git a/src-tauri/src/core/prfitem.rs b/src-tauri/src/core/prfitem.rs
index 3d99906..48f5a6c 100644
--- a/src-tauri/src/core/prfitem.rs
+++ b/src-tauri/src/core/prfitem.rs
@@ -60,7 +60,7 @@ pub struct PrfExtra {
   pub expire: usize,
 }
 
-#[derive(Default, Debug, Clone, Deserialize, Serialize)]
+#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
 pub struct PrfOption {
   /// for `remote` profile's http request
   /// see issue #13
diff --git a/src-tauri/src/core/profiles.rs b/src-tauri/src/core/profiles.rs
index af37f9c..fe00c50 100644
--- a/src-tauri/src/core/profiles.rs
+++ b/src-tauri/src/core/profiles.rs
@@ -47,6 +47,7 @@ impl Profiles {
       profiles.items = Some(vec![]);
     }
 
+    // compatiable with the old old old version
     profiles.items.as_mut().map(|items| {
       for mut item in items.iter_mut() {
         if item.uid.is_none() {
@@ -67,19 +68,6 @@ impl Profiles {
     )
   }
 
-  /// sync the config between file and memory
-  pub fn sync_file(&mut self) -> Result<()> {
-    let data = Self::read_file();
-    if data.current.is_none() && data.items.is_none() {
-      bail!("failed to read profiles.yaml");
-    }
-
-    self.current = data.current;
-    self.chain = data.chain;
-    self.items = data.items;
-    Ok(())
-  }
-
   /// get the current uid
   pub fn get_current(&self) -> Option<String> {
     self.current.clone()
@@ -94,11 +82,9 @@ impl Profiles {
     let items = self.items.as_ref().unwrap();
     let some_uid = Some(uid.clone());
 
-    for each in items.iter() {
-      if each.uid == some_uid {
-        self.current = some_uid;
-        return self.save_file();
-      }
+    if items.iter().find(|&each| each.uid == some_uid).is_some() {
+      self.current = some_uid;
+      return self.save_file();
     }
 
     bail!("invalid uid \"{uid}\"");
@@ -162,7 +148,7 @@ impl Profiles {
     self.save_file()
   }
 
-  /// update the item's value
+  /// update the item value
   pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
     let mut items = self.items.take().unwrap_or(vec![]);
 
@@ -197,7 +183,7 @@ impl Profiles {
     // find the item
     let _ = self.get_item(&uid)?;
 
-    self.items.as_mut().map(|items| {
+    if let Some(items) = self.items.as_mut() {
       let some_uid = Some(uid.clone());
 
       for mut each in items.iter_mut() {
@@ -217,15 +203,15 @@ impl Profiles {
             let path = dirs::app_profiles_dir().join(&file);
 
             fs::File::create(path)
-              .unwrap()
+              .context(format!("failed to create file \"{}\"", file))?
               .write(file_data.as_bytes())
-              .unwrap();
+              .context(format!("failed to write to file \"{}\"", file))?;
           }
 
           break;
         }
       }
-    });
+    }
 
     self.save_file()
   }
diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs
index b1eb42e..468037e 100644
--- a/src-tauri/src/core/service.rs
+++ b/src-tauri/src/core/service.rs
@@ -57,6 +57,8 @@ impl Service {
     self.start()
   }
 
+  /// update clash config
+  /// using PUT methods
   pub fn set_config(&self, info: ClashInfo, config: Mapping, notice: Notice) -> Result<()> {
     if self.sidecar.is_none() {
       bail!("did not start sidecar");
diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs
new file mode 100644
index 0000000..8a6d7a8
--- /dev/null
+++ b/src-tauri/src/core/sysopt.rs
@@ -0,0 +1,207 @@
+use super::{Clash, Verge};
+use crate::{log_if_err, utils::sysopt::SysProxyConfig};
+use anyhow::{bail, Result};
+use auto_launch::{AutoLaunch, AutoLaunchBuilder};
+// use parking_lot::Mutex;
+use std::sync::Arc;
+use tauri::{async_runtime::Mutex, utils::platform::current_exe};
+
+pub struct Sysopt {
+  /// current system proxy setting
+  cur_sysproxy: Option<SysProxyConfig>,
+
+  /// record the original system proxy
+  /// recover it when exit
+  old_sysproxy: Option<SysProxyConfig>,
+
+  /// helps to auto launch the app
+  auto_launch: Option<AutoLaunch>,
+
+  /// record whether the guard async is running or not
+  guard_state: Arc<Mutex<bool>>,
+}
+
+impl Sysopt {
+  pub fn new() -> Sysopt {
+    Sysopt {
+      cur_sysproxy: None,
+      old_sysproxy: None,
+      auto_launch: None,
+      guard_state: Arc::new(Mutex::new(false)),
+    }
+  }
+
+  /// init the sysproxy
+  pub fn init_sysproxy(&mut self, port: Option<String>, verge: &Verge) {
+    if let Some(port) = port {
+      let enable = verge.config.enable_system_proxy.clone().unwrap_or(false);
+
+      self.old_sysproxy = match SysProxyConfig::get_sys() {
+        Ok(proxy) => Some(proxy),
+        Err(_) => None,
+      };
+
+      let bypass = verge.config.system_proxy_bypass.clone();
+      let sysproxy = SysProxyConfig::new(enable, port, bypass);
+
+      if enable {
+        if let Err(err) = sysproxy.set_sys() {
+          log::error!("failed to set system proxy for `{err}`");
+        }
+      }
+
+      self.cur_sysproxy = Some(sysproxy);
+    }
+
+    // launchs the system proxy guard
+    self.guard_proxy();
+  }
+
+  /// update the system proxy
+  /// when the verge config is changed
+  pub fn update_sysproxy(&mut self, enable: Option<bool>, bypass: Option<String>) -> Result<()> {
+    let sysproxy = self.cur_sysproxy.take();
+
+    if sysproxy.is_none() {
+      bail!("unhandle error for sysproxy is none");
+    }
+
+    let mut sysproxy = sysproxy.unwrap();
+
+    if let Some(enable) = enable {
+      sysproxy.enable = enable;
+    }
+
+    if let Some(bypass) = bypass {
+      sysproxy.bypass = bypass;
+    }
+
+    self.cur_sysproxy = Some(sysproxy);
+
+    if self.cur_sysproxy.as_ref().unwrap().set_sys().is_err() {
+      bail!("failed to set system proxy");
+    }
+
+    Ok(())
+  }
+
+  /// reset the sysproxy
+  pub fn reset_sysproxy(&mut self) {
+    if let Some(sysproxy) = self.old_sysproxy.take() {
+      match sysproxy.set_sys() {
+        Ok(_) => self.cur_sysproxy = None,
+        Err(_) => log::error!("failed to reset proxy"),
+      }
+    }
+  }
+
+  /// get current proxy
+  pub fn get_sysproxy(&self) -> Result<Option<SysProxyConfig>> {
+    Ok(self.cur_sysproxy.clone())
+  }
+
+  /// init the auto launch
+  pub fn init_launch(&mut self, enable: Option<bool>) -> Result<()> {
+    let app_exe = current_exe().unwrap();
+    let app_exe = dunce::canonicalize(app_exe).unwrap();
+    let app_name = app_exe.file_stem().unwrap().to_str().unwrap();
+    let app_path = app_exe.as_os_str().to_str().unwrap();
+
+    // fix issue #26
+    #[cfg(target_os = "windows")]
+    let app_path = format!("\"{app_path}\"");
+    #[cfg(target_os = "windows")]
+    let app_path = app_path.as_str();
+
+    let auto = AutoLaunchBuilder::new()
+      .set_app_name(app_name)
+      .set_app_path(app_path)
+      .build();
+
+    if let Some(enable) = enable {
+      // fix issue #26
+      if enable {
+        auto.enable()?;
+      }
+    }
+
+    self.auto_launch = Some(auto);
+
+    Ok(())
+  }
+
+  /// update the startup
+  pub fn update_launch(&mut self, enable: Option<bool>) -> Result<()> {
+    if enable.is_none() {
+      return Ok(());
+    }
+
+    let enable = enable.unwrap();
+    let auto_launch = self.auto_launch.as_ref().unwrap();
+
+    match enable {
+      true => auto_launch.enable()?,
+      false => auto_launch.disable()?,
+    };
+
+    Ok(())
+  }
+
+  /// launch a system proxy guard
+  /// read config from file directly
+  pub fn guard_proxy(&self) {
+    use tokio::time::{sleep, Duration};
+
+    let guard_state = self.guard_state.clone();
+
+    tauri::async_runtime::spawn(async move {
+      // if it is running, exit
+      let mut state = guard_state.lock().await;
+      if *state {
+        return;
+      }
+      *state = true;
+      drop(state);
+
+      // default duration is 10s
+      let mut wait_secs = 10u64;
+
+      loop {
+        sleep(Duration::from_secs(wait_secs)).await;
+
+        log::debug!("guard heartbeat detection");
+
+        let verge = Verge::new();
+
+        let enable_proxy = verge.config.enable_system_proxy.unwrap_or(false);
+        let enable_guard = verge.config.enable_proxy_guard.unwrap_or(false);
+        let guard_duration = verge.config.proxy_guard_duration.unwrap_or(10);
+
+        // update duration
+        wait_secs = guard_duration;
+
+        // stop loop
+        if !enable_guard || !enable_proxy {
+          break;
+        }
+
+        log::info!("try to guard proxy");
+
+        let clash = Clash::new();
+
+        match &clash.info.port {
+          Some(port) => {
+            let bypass = verge.config.system_proxy_bypass.clone();
+            let sysproxy = SysProxyConfig::new(true, port.clone(), bypass);
+
+            log_if_err!(sysproxy.set_sys());
+          }
+          None => log::error!("failed to parse clash port"),
+        }
+      }
+
+      let mut state = guard_state.lock().await;
+      *state = false;
+    });
+  }
+}
diff --git a/src-tauri/src/core/verge.rs b/src-tauri/src/core/verge.rs
index eaa789c..f96b40f 100644
--- a/src-tauri/src/core/verge.rs
+++ b/src-tauri/src/core/verge.rs
@@ -1,14 +1,7 @@
 use crate::log_if_err;
-use crate::{
-  core::Clash,
-  utils::{config, dirs, sysopt::SysProxyConfig},
-};
+use crate::utils::{config, dirs};
 use anyhow::{bail, Result};
-use auto_launch::{AutoLaunch, AutoLaunchBuilder};
 use serde::{Deserialize, Serialize};
-use std::sync::Arc;
-use tauri::{async_runtime::Mutex, utils::platform::current_exe};
-use tauri::{AppHandle, Manager};
 
 /// ### `verge.yaml` schema
 #[derive(Default, Debug, Clone, Deserialize, Serialize)]
@@ -87,19 +80,6 @@ impl VergeConfig {
 pub struct Verge {
   /// manage the verge config
   pub config: VergeConfig,
-
-  /// current system proxy setting
-  pub cur_sysproxy: Option<SysProxyConfig>,
-
-  /// record the original system proxy
-  /// recover it when exit
-  old_sysproxy: Option<SysProxyConfig>,
-
-  /// helps to auto launch the app
-  auto_launch: Option<AutoLaunch>,
-
-  /// record whether the guard async is running or not
-  guard_state: Arc<Mutex<bool>>,
 }
 
 impl Default for Verge {
@@ -112,100 +92,11 @@ impl Verge {
   pub fn new() -> Self {
     Verge {
       config: VergeConfig::new(),
-      old_sysproxy: None,
-      cur_sysproxy: None,
-      auto_launch: None,
-      guard_state: Arc::new(Mutex::new(false)),
     }
   }
 
-  /// init the sysproxy
-  pub fn init_sysproxy(&mut self, port: Option<String>) {
-    if let Some(port) = port {
-      let enable = self.config.enable_system_proxy.clone().unwrap_or(false);
-
-      self.old_sysproxy = match SysProxyConfig::get_sys() {
-        Ok(proxy) => Some(proxy),
-        Err(_) => None,
-      };
-
-      let bypass = self.config.system_proxy_bypass.clone();
-      let sysproxy = SysProxyConfig::new(enable, port, bypass);
-
-      if enable {
-        if sysproxy.set_sys().is_err() {
-          log::error!("failed to set system proxy");
-        }
-      }
-
-      self.cur_sysproxy = Some(sysproxy);
-    }
-
-    // launchs the system proxy guard
-    Verge::guard_proxy(self.guard_state.clone());
-  }
-
-  /// reset the sysproxy
-  pub fn reset_sysproxy(&mut self) {
-    if let Some(sysproxy) = self.old_sysproxy.take() {
-      match sysproxy.set_sys() {
-        Ok(_) => self.cur_sysproxy = None,
-        Err(_) => log::error!("failed to reset proxy"),
-      }
-    }
-  }
-
-  /// init the auto launch
-  pub fn init_launch(&mut self) -> Result<()> {
-    let app_exe = current_exe().unwrap();
-    let app_exe = dunce::canonicalize(app_exe).unwrap();
-    let app_name = app_exe.file_stem().unwrap().to_str().unwrap();
-    let app_path = app_exe.as_os_str().to_str().unwrap();
-
-    // fix issue #26
-    #[cfg(target_os = "windows")]
-    let app_path = format!("\"{app_path}\"");
-    #[cfg(target_os = "windows")]
-    let app_path = app_path.as_str();
-
-    let auto = AutoLaunchBuilder::new()
-      .set_app_name(app_name)
-      .set_app_path(app_path)
-      .build();
-
-    if let Some(enable) = self.config.enable_auto_launch.as_ref() {
-      // fix issue #26
-      if *enable {
-        auto.enable()?;
-      }
-    }
-
-    self.auto_launch = Some(auto);
-
-    Ok(())
-  }
-
-  /// update the startup
-  fn update_launch(&mut self, enable: bool) -> Result<()> {
-    let conf_enable = self.config.enable_auto_launch.clone().unwrap_or(false);
-
-    if enable == conf_enable {
-      return Ok(());
-    }
-
-    let auto_launch = self.auto_launch.clone().unwrap();
-
-    match enable {
-      true => auto_launch.enable()?,
-      false => auto_launch.disable()?,
-    };
-
-    Ok(())
-  }
-
   /// patch verge config
-  /// There should be only one update at a time here
-  /// so call the save_file at the end is savely
+  /// only save to file
   pub fn patch_config(&mut self, patch: VergeConfig) -> Result<()> {
     // only change it
     if patch.language.is_some() {
@@ -217,62 +108,28 @@ impl Verge {
     if patch.theme_blur.is_some() {
       self.config.theme_blur = patch.theme_blur;
     }
-    if patch.traffic_graph.is_some() {
-      self.config.traffic_graph = patch.traffic_graph;
-    }
-    if patch.enable_silent_start.is_some() {
-      self.config.enable_silent_start = patch.enable_silent_start;
-    }
     if patch.theme_setting.is_some() {
       self.config.theme_setting = patch.theme_setting;
     }
+    if patch.traffic_graph.is_some() {
+      self.config.traffic_graph = patch.traffic_graph;
+    }
 
-    // should update system startup
+    // system setting
+    if patch.enable_silent_start.is_some() {
+      self.config.enable_silent_start = patch.enable_silent_start;
+    }
     if patch.enable_auto_launch.is_some() {
-      let enable = patch.enable_auto_launch.unwrap();
-      self.update_launch(enable)?;
-      self.config.enable_auto_launch = Some(enable);
+      self.config.enable_auto_launch = patch.enable_auto_launch;
     }
 
-    // should update system proxy
+    // proxy
     if patch.enable_system_proxy.is_some() {
-      let enable = patch.enable_system_proxy.unwrap();
-
-      if let Some(mut sysproxy) = self.cur_sysproxy.take() {
-        sysproxy.enable = enable;
-        if sysproxy.set_sys().is_err() {
-          self.cur_sysproxy = Some(sysproxy);
-
-          bail!("failed to set system proxy");
-        }
-        self.cur_sysproxy = Some(sysproxy);
-      }
-      self.config.enable_system_proxy = Some(enable);
+      self.config.enable_system_proxy = patch.enable_system_proxy;
     }
-
-    // should update system proxy too
     if patch.system_proxy_bypass.is_some() {
-      let bypass = patch.system_proxy_bypass.unwrap();
-
-      if let Some(mut sysproxy) = self.cur_sysproxy.take() {
-        if sysproxy.enable {
-          sysproxy.bypass = bypass.clone();
-
-          if sysproxy.set_sys().is_err() {
-            self.cur_sysproxy = Some(sysproxy);
-
-            bail!("failed to set system proxy");
-          }
-        }
-
-        self.cur_sysproxy = Some(sysproxy);
-      }
-
-      self.config.system_proxy_bypass = Some(bypass);
+      self.config.system_proxy_bypass = patch.system_proxy_bypass;
     }
-
-    // proxy guard
-    // only change it
     if patch.enable_proxy_guard.is_some() {
       self.config.enable_proxy_guard = patch.enable_proxy_guard;
     }
@@ -280,103 +137,11 @@ impl Verge {
       self.config.proxy_guard_duration = patch.proxy_guard_duration;
     }
 
-    // relaunch the guard
-    if patch.enable_system_proxy.is_some() || patch.enable_proxy_guard.is_some() {
-      Verge::guard_proxy(self.guard_state.clone());
-    }
-
-    // handle the tun mode
+    // tun mode
     if patch.enable_tun_mode.is_some() {
       self.config.enable_tun_mode = patch.enable_tun_mode;
     }
 
     self.config.save_file()
   }
-
-  /// update the system tray state
-  pub fn update_systray(&self, app_handle: &AppHandle) -> Result<()> {
-    // system proxy
-    let system_proxy = self.config.enable_system_proxy.as_ref();
-    system_proxy.map(|system_proxy| {
-      app_handle
-        .tray_handle()
-        .get_item("system_proxy")
-        .set_selected(*system_proxy)
-        .unwrap();
-    });
-
-    // tun mode
-    let tun_mode = self.config.enable_tun_mode.as_ref();
-    tun_mode.map(|tun_mode| {
-      app_handle
-        .tray_handle()
-        .get_item("tun_mode")
-        .set_selected(*tun_mode)
-        .unwrap();
-    });
-
-    // update verge config
-    let window = app_handle.get_window("main").unwrap();
-    window.emit("verge://refresh-verge-config", "yes").unwrap();
-
-    Ok(())
-  }
-}
-
-impl Verge {
-  /// launch a system proxy guard
-  /// read config from file directly
-  pub fn guard_proxy(guard_state: Arc<Mutex<bool>>) {
-    use tokio::time::{sleep, Duration};
-
-    tauri::async_runtime::spawn(async move {
-      // if it is running, exit
-      let mut state = guard_state.lock().await;
-      if *state {
-        return;
-      }
-      *state = true;
-      std::mem::drop(state);
-
-      // default duration is 10s
-      let mut wait_secs = 10u64;
-
-      loop {
-        sleep(Duration::from_secs(wait_secs)).await;
-
-        log::debug!("guard heartbeat detection");
-
-        let verge = Verge::new();
-
-        let enable_proxy = verge.config.enable_system_proxy.unwrap_or(false);
-        let enable_guard = verge.config.enable_proxy_guard.unwrap_or(false);
-        let guard_duration = verge.config.proxy_guard_duration.unwrap_or(10);
-
-        // update duration
-        wait_secs = guard_duration;
-
-        // stop loop
-        if !enable_guard || !enable_proxy {
-          break;
-        }
-
-        log::info!("try to guard proxy");
-
-        let clash = Clash::new();
-
-        match &clash.info.port {
-          Some(port) => {
-            let bypass = verge.config.system_proxy_bypass.clone();
-            let sysproxy = SysProxyConfig::new(true, port.clone(), bypass);
-
-            log_if_err!(sysproxy.set_sys());
-          }
-          None => log::error!("fail to parse clash port"),
-        }
-      }
-
-      let mut state = guard_state.lock().await;
-      *state = false;
-    });
-  }
 }
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 97b777e..9e0e0a8 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -51,33 +51,33 @@ fn main() -> std::io::Result<()> {
         }
         "system_proxy" => {
           let core = app_handle.state::<core::Core>();
-          let mut verge = core.verge.lock();
 
-          let old_value = verge.config.enable_system_proxy.clone().unwrap_or(false);
-          let new_value = !old_value;
+          let new_value = {
+            let verge = core.verge.lock();
+            !verge.config.enable_system_proxy.clone().unwrap_or(false)
+          };
 
-          match verge.patch_config(VergeConfig {
+          let patch = VergeConfig {
             enable_system_proxy: Some(new_value),
             ..VergeConfig::default()
-          }) {
-            Ok(_) => verge.update_systray(app_handle).unwrap(),
-            Err(err) => log::error!("{err}"),
-          }
+          };
+
+          crate::log_if_err!(core.patch_verge(patch, app_handle));
         }
         "tun_mode" => {
           let core = app_handle.state::<core::Core>();
-          let mut verge = core.verge.lock();
 
-          let old_value = verge.config.enable_tun_mode.clone().unwrap_or(false);
-          let new_value = !old_value;
+          let new_value = {
+            let verge = core.verge.lock();
+            !verge.config.enable_tun_mode.clone().unwrap_or(false)
+          };
 
-          match verge.patch_config(VergeConfig {
+          let patch = VergeConfig {
             enable_tun_mode: Some(new_value),
             ..VergeConfig::default()
-          }) {
-            Ok(_) => verge.update_systray(app_handle).unwrap(),
-            Err(err) => log::error!("{err}"),
-          }
+          };
+
+          crate::log_if_err!(core.patch_verge(patch, app_handle));
         }
         "restart_clash" => {
           let core = app_handle.state::<core::Core>();
@@ -104,7 +104,7 @@ fn main() -> std::io::Result<()> {
       cmds::restart_sidecar,
       cmds::get_sys_proxy,
       cmds::get_cur_proxy,
-      cmds::kill_sidecars,
+      cmds::kill_sidecar,
       cmds::open_app_dir,
       cmds::open_logs_dir,
       // clash
@@ -122,7 +122,6 @@ fn main() -> std::io::Result<()> {
       cmds::delete_profile,
       cmds::select_profile,
       cmds::get_profiles,
-      cmds::sync_profiles,
       cmds::enhance_profiles,
       cmds::change_profile_chain,
       cmds::change_profile_valid,
diff --git a/src-tauri/src/utils/help.rs b/src-tauri/src/utils/help.rs
index 0613407..0b7dacb 100644
--- a/src-tauri/src/utils/help.rs
+++ b/src-tauri/src/utils/help.rs
@@ -1,4 +1,7 @@
+use anyhow::{bail, Result};
 use nanoid::nanoid;
+use std::path::PathBuf;
+use std::process::Command;
 use std::str::FromStr;
 use std::time::{SystemTime, UNIX_EPOCH};
 
@@ -41,6 +44,38 @@ pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
   }
 }
 
+/// open file
+/// use vscode by default
+pub fn open_file(path: PathBuf) -> Result<()> {
+  // use vscode first
+  if let Ok(code) = which::which("code") {
+    #[cfg(target_os = "windows")]
+    {
+      use std::os::windows::process::CommandExt;
+
+      if let Err(err) = Command::new(code)
+        .creation_flags(0x08000000)
+        .arg(path)
+        .spawn()
+      {
+        bail!(format!("failed to open file by VScode for `{err}`"));
+      }
+    }
+
+    #[cfg(not(target_os = "windows"))]
+    if let Err(err) = Command::new(code).arg(path).spawn() {
+      bail!(format!("failed to open file by VScode for `{err}`"));
+    }
+
+    return Ok(());
+  }
+
+  match open::that(path) {
+    Ok(_) => Ok(()),
+    Err(err) => bail!(format!("failed to open file for `{err}`")),
+  }
+}
+
 #[macro_export]
 macro_rules! log_if_err {
   ($result: expr) => {
diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs
index 0eb69c4..a7d6906 100644
--- a/src-tauri/src/utils/resolve.rs
+++ b/src-tauri/src/utils/resolve.rs
@@ -21,9 +21,9 @@ pub fn resolve_setup(app: &App) {
 /// reset system proxy
 pub fn resolve_reset(app_handle: &AppHandle) {
   let core = app_handle.state::<Core>();
-  let mut verge = core.verge.lock();
+  let mut sysopt = core.sysopt.lock();
 
-  verge.reset_sysproxy();
+  sysopt.reset_sysproxy();
 }
 
 /// customize the window theme
diff --git a/src/components/layout/update-dialog.tsx b/src/components/layout/update-dialog.tsx
index 2d2c3f4..f35de41 100644
--- a/src/components/layout/update-dialog.tsx
+++ b/src/components/layout/update-dialog.tsx
@@ -13,7 +13,7 @@ import {
 } from "@mui/material";
 import { relaunch } from "@tauri-apps/api/process";
 import { checkUpdate, installUpdate } from "@tauri-apps/api/updater";
-import { killSidecars, restartSidecar } from "../../services/cmds";
+import { killSidecar, restartSidecar } from "../../services/cmds";
 import { atomUpdateState } from "../../services/states";
 import Notice from "../base/base-notice";
 
@@ -41,7 +41,7 @@ const UpdateDialog = (props: Props) => {
     setUpdateState(true);
 
     try {
-      await killSidecars();
+      await killSidecar();
       await installUpdate();
       await relaunch();
     } catch (err: any) {
diff --git a/src/services/cmds.ts b/src/services/cmds.ts
index bc4555e..4cc2559 100644
--- a/src/services/cmds.ts
+++ b/src/services/cmds.ts
@@ -6,10 +6,6 @@ export async function getProfiles() {
   return invoke<CmdType.ProfilesConfig>("get_profiles");
 }
 
-export async function syncProfiles() {
-  return invoke<void>("sync_profiles");
-}
-
 export async function enhanceProfiles() {
   return invoke<void>("enhance_profiles");
 }
@@ -94,8 +90,8 @@ export async function restartSidecar() {
   return invoke<void>("restart_sidecar");
 }
 
-export async function killSidecars() {
-  return invoke<any>("kill_sidecars");
+export async function killSidecar() {
+  return invoke<any>("kill_sidecar");
 }
 
 export async function openAppDir() {