diff --git a/src-tauri/src/cmds/profile.rs b/src-tauri/src/cmds/profile.rs
index 39f28b001830f175da95ae06f284e3f042406c53..576805c30ae5f99437b284cd1c9396431974efc2 100644
--- a/src-tauri/src/cmds/profile.rs
+++ b/src-tauri/src/cmds/profile.rs
@@ -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")),
+pub async fn update_profile(
+  index: usize,
+  profiles: State<'_, ProfilesState>,
+) -> Result<(), String> {
+  // 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("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()),
+    }
   };
 
-  let result = match fetch_profile(&url).await {
+  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)),
   };
 
-  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(
-  index: usize,
-  profile: ProfileItem,
-  lock: State<'_, ProfileLock>,
-) -> Result<(), String> {
-  // get lock
-  if lock.0.lock().is_err() {
-    return Err(format!("can not get file lock"));
-  }
-
-  let mut profiles = read_profiles();
-  let mut items = profiles.items.unwrap_or(vec![]);
-
-  if index >= items.len() {
-    return Err(format!("the index out of bound"));
-  }
-
-  if profile.name.is_some() {
-    items[index].name = profile.name;
-  }
-  if profile.file.is_some() {
-    items[index].file = profile.file;
-  }
-  if profile.mode.is_some() {
-    items[index].mode = profile.mode;
-  }
-  if profile.url.is_some() {
-    items[index].url = profile.url;
+  match profiles.0.lock() {
+    Ok(mut profiles) => profiles.update_item(index, result),
+    Err(_) => Err("can not get profiles lock".into()),
   }
-  if profile.selected.is_some() {
-    items[index].selected = profile.selected;
-  }
-  if profile.extra.is_some() {
-    items[index].extra = profile.extra;
-  }
-
-  profiles.items = Some(items);
-  save_profiles(&profiles)
 }
 
 /// change the current profile
 #[tauri::command]
-pub async fn put_profiles(
-  current: usize,
-  lock: State<'_, ProfileLock>,
+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,
-  };
+  clash::put_clash_profile(&arc).await
+}
 
-  if current >= items_len {
-    return Err(format!("the index out of bound"));
+/// 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()),
   }
+}
 
-  profiles.current = Some(current);
-  match save_profiles(&profiles) {
-    Ok(_) => put_clash_profile(&clash_info).await,
-    Err(err) => Err(err),
+/// patch the profile config
+#[tauri::command]
+pub fn patch_profile(
+  index: usize,
+  profile: ProfileItem,
+  profiles: State<'_, ProfilesState>,
+) -> Result<(), String> {
+  match profiles.0.lock() {
+    Ok(mut profiles) => profiles.patch_item(index, profile),
+    Err(_) => Err("can not get profiles lock".into()),
   }
 }
diff --git a/src-tauri/src/config/profiles.rs b/src-tauri/src/config/profiles.rs
index e83b35a637ebd620543861a9f4cb2c5516cd3f21..878590b0a6e5d479ca4535cc1c22e81463ccc2c0 100644
--- a/src-tauri/src/config/profiles.rs
+++ b/src-tauri/src/config/profiles.rs
@@ -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()
+  }
+}
diff --git a/src-tauri/src/events/state.rs b/src-tauri/src/events/state.rs
index 01b1b30de72bcfca07f7a2ff07f147477ad9d06e..cfda63b8b2a0558579447501c86bbebe1bf3a98f 100644
--- a/src-tauri/src/events/state.rs
+++ b/src-tauri/src/events/state.rs
@@ -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>>);
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index c42540313d4c21aef00078e12186d288379dd769..debbe79e8c787efdef33c04e93932404f5cbc82b 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -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")
diff --git a/src-tauri/src/utils/config.rs b/src-tauri/src/utils/config.rs
index 194344a4cc89f9b6713862901001941441b9590b..78b8030bcbc353b97966cd0c438765784f3bb4ff 100644
--- a/src-tauri/src/utils/config.rs
+++ b/src-tauri/src/utils/config.rs
@@ -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"))
diff --git a/src/components/proxy-group.tsx b/src/components/proxy-group.tsx
index b9f1fa205c2aae64edc9b9be0cc62b5ad77007cb..090539a00932fca7df04ed3e44cb0b0b0caaf0a2 100644
--- a/src/components/proxy-group.tsx
+++ b/src/components/proxy-group.tsx
@@ -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
diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx
index 11f15446993761b06fa39091d88e5e729ff12517..e37450964776a6f4d8fa752b9e73893cddf17f59 100644
--- a/src/pages/profile.tsx
+++ b/src/pages/profile.tsx
@@ -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);
       })
diff --git a/src/services/cmds.ts b/src/services/cmds.ts
index 810009279e98aa510ac04dd5a14362ed25341853..132cfb29c7fbf62208996142fb7c58f9fd2deac9 100644
--- a/src/services/cmds.ts
+++ b/src/services/cmds.ts
@@ -1,16 +1,12 @@
 import { invoke } from "@tauri-apps/api/tauri";
 import { ApiType, CmdType } from "./types";
 
-export async function restartSidecar() {
-  return invoke<void>("restart_sidecar");
-}
-
-export async function getClashInfo() {
-  return invoke<CmdType.ClashInfo | null>("get_clash_info");
+export async function getProfiles() {
+  return (await invoke<CmdType.ProfilesConfig>("get_profiles")) ?? {};
 }
 
-export async function patchClashConfig(payload: Partial<ApiType.ConfigData>) {
-  return invoke<void>("patch_clash_config", { payload });
+export async function syncProfiles() {
+  return invoke<void>("sync_profiles");
 }
 
 export async function importProfile(url: string) {
@@ -21,16 +17,31 @@ 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 deleteProfile(index: number) {
+  return invoke<void>("delete_profile", { index });
 }
 
-export async function setProfiles(index: number, profile: CmdType.ProfileItem) {
-  return invoke<void>("set_profiles", { index, profile });
+export async function patchProfile(
+  index: number,
+  profile: CmdType.ProfileItem
+) {
+  return invoke<void>("patch_profile", { index, profile });
 }
 
-export async function putProfiles(current: number) {
-  return invoke<void>("put_profiles", { current });
+export async function selectProfile(index: number) {
+  return invoke<void>("select_profile", { index });
+}
+
+export async function restartSidecar() {
+  return invoke<void>("restart_sidecar");
+}
+
+export async function getClashInfo() {
+  return invoke<CmdType.ClashInfo | null>("get_clash_info");
+}
+
+export async function patchClashConfig(payload: Partial<ApiType.ConfigData>) {
+  return invoke<void>("patch_clash_config", { payload });
 }
 
 export async function setSysProxy(enable: boolean) {