mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-28 07:14:40 +08:00
refactor: profiles use IndexMap
This commit is contained in:
57
Cargo.lock
generated
57
Cargo.lock
generated
@@ -676,7 +676,7 @@ dependencies = [
|
||||
"boa_interner",
|
||||
"boa_macros",
|
||||
"boa_string",
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"num-bigint",
|
||||
"rustc-hash",
|
||||
]
|
||||
@@ -706,9 +706,9 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-concurrency",
|
||||
"futures-lite 2.6.1",
|
||||
"hashbrown 0.16.0",
|
||||
"hashbrown 0.16.1",
|
||||
"icu_normalizer",
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"intrusive-collections",
|
||||
"itertools 0.14.0",
|
||||
"num-bigint",
|
||||
@@ -741,7 +741,7 @@ checksum = "f1179f690cbfcbe5364cceee5f1cb577265bb6f07b0be6f210aabe270adcf9da"
|
||||
dependencies = [
|
||||
"boa_macros",
|
||||
"boa_string",
|
||||
"hashbrown 0.16.0",
|
||||
"hashbrown 0.16.1",
|
||||
"thin-vec",
|
||||
]
|
||||
|
||||
@@ -753,8 +753,8 @@ checksum = "9626505d33dc63d349662437297df1d3afd9d5fc4a2b3ad34e5e1ce879a78848"
|
||||
dependencies = [
|
||||
"boa_gc",
|
||||
"boa_macros",
|
||||
"hashbrown 0.16.0",
|
||||
"indexmap 2.12.0",
|
||||
"hashbrown 0.16.1",
|
||||
"indexmap 2.12.1",
|
||||
"once_cell",
|
||||
"phf 0.13.1",
|
||||
"rustc-hash",
|
||||
@@ -1131,6 +1131,7 @@ dependencies = [
|
||||
"futures",
|
||||
"gethostname",
|
||||
"getrandom 0.3.4",
|
||||
"indexmap 2.12.1",
|
||||
"log",
|
||||
"nanoid",
|
||||
"network-interface",
|
||||
@@ -3002,7 +3003,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -3021,7 +3022,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.3.1",
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -3078,9 +3079,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
@@ -3594,12 +3595,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.12.0"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
|
||||
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.0",
|
||||
"hashbrown 0.16.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -3904,7 +3905,7 @@ checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2"
|
||||
dependencies = [
|
||||
"cssparser",
|
||||
"html5ever",
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"selectors",
|
||||
]
|
||||
|
||||
@@ -5166,7 +5167,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5401,7 +5402,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"quick-xml 0.38.4",
|
||||
"serde",
|
||||
"time",
|
||||
@@ -6067,7 +6068,7 @@ version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2057b2325e68a893284d1538021ab90279adac1139957ca2a74426c6f118fb48"
|
||||
dependencies = [
|
||||
"hashbrown 0.16.0",
|
||||
"hashbrown 0.16.1",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
@@ -6713,7 +6714,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"schemars 0.9.0",
|
||||
"schemars 1.1.0",
|
||||
"serde_core",
|
||||
@@ -6740,7 +6741,7 @@ version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
@@ -6753,7 +6754,7 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f"
|
||||
dependencies = [
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
@@ -8209,7 +8210,7 @@ version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
|
||||
dependencies = [
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"serde_core",
|
||||
"serde_spanned 1.0.3",
|
||||
"toml_datetime 0.7.3",
|
||||
@@ -8242,7 +8243,7 @@ version = "0.19.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||
dependencies = [
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"toml_datetime 0.6.11",
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
@@ -8253,7 +8254,7 @@ version = "0.20.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
|
||||
dependencies = [
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"toml_datetime 0.6.11",
|
||||
"winnow 0.5.40",
|
||||
]
|
||||
@@ -8264,7 +8265,7 @@ version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"serde",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
@@ -8278,7 +8279,7 @@ version = "0.23.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d"
|
||||
dependencies = [
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"toml_datetime 0.7.3",
|
||||
"toml_parser",
|
||||
"winnow 0.7.13",
|
||||
@@ -8433,7 +8434,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"sync_wrapper 1.0.2",
|
||||
@@ -10166,7 +10167,7 @@ checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
@@ -10185,7 +10186,7 @@ dependencies = [
|
||||
"flate2",
|
||||
"getrandom 0.3.4",
|
||||
"hmac",
|
||||
"indexmap 2.12.0",
|
||||
"indexmap 2.12.1",
|
||||
"lzma-rust2",
|
||||
"memchr",
|
||||
"pbkdf2",
|
||||
|
||||
@@ -64,6 +64,7 @@ log = "0.4.28"
|
||||
|
||||
smartstring = { version = "1.0.1" }
|
||||
compact_str = { version = "0.9.0", features = ["serde"] }
|
||||
indexmap = { version = "2.12.1" }
|
||||
|
||||
serde = { version = "1.0.228" }
|
||||
serde_json = { version = "1.0.145" }
|
||||
|
||||
@@ -52,6 +52,7 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml_ng = { workspace = true }
|
||||
smartstring = { workspace = true, features = ["serde"] }
|
||||
indexmap = { workspace = true, features = ["serde"] } #TODO 迁移完成后移除
|
||||
warp = { version = "0.4.2", features = ["server"] }
|
||||
open = "5.3.3"
|
||||
dunce = "1.0.5"
|
||||
|
||||
@@ -5,20 +5,22 @@ use crate::utils::{
|
||||
};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_yaml_ng::Mapping;
|
||||
use smartstring::alias::String;
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
use tokio::fs;
|
||||
|
||||
/// Define the `profiles.yaml` schema
|
||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||
#[derive(Default, Debug, Clone, Serialize)]
|
||||
pub struct IProfiles {
|
||||
/// same as PrfConfig.current
|
||||
pub current: Option<String>,
|
||||
|
||||
/// profile list
|
||||
pub items: Option<Vec<PrfItem>>,
|
||||
#[serde(default, deserialize_with = "deserialize_items")]
|
||||
pub items: Option<IndexMap<String, PrfItem>>,
|
||||
}
|
||||
|
||||
pub struct IProfilePreview<'a> {
|
||||
@@ -45,16 +47,8 @@ macro_rules! patch {
|
||||
|
||||
impl IProfiles {
|
||||
// Helper to find and remove an item by uid from the items vec, returning its file name (if any).
|
||||
fn take_item_file_by_uid(
|
||||
items: &mut Vec<PrfItem>,
|
||||
target_uid: Option<String>,
|
||||
) -> Option<String> {
|
||||
for (i, _) in items.iter().enumerate() {
|
||||
if items[i].uid == target_uid {
|
||||
return items.remove(i).file;
|
||||
}
|
||||
}
|
||||
None
|
||||
fn take_item_by_uid(&mut self, target_uid: Option<&String>) -> Option<PrfItem> {
|
||||
self.items.as_mut()?.shift_remove(target_uid?)
|
||||
}
|
||||
|
||||
pub async fn new() -> Self {
|
||||
@@ -68,12 +62,29 @@ impl IProfiles {
|
||||
|
||||
match help::read_yaml::<Self>(&path).await {
|
||||
Ok(mut profiles) => {
|
||||
let items = profiles.items.get_or_insert_with(Vec::new);
|
||||
for item in items.iter_mut() {
|
||||
let items = profiles.items.get_or_insert_with(IndexMap::new);
|
||||
let mut needs_save = false;
|
||||
|
||||
for item in items.values_mut() {
|
||||
if item.uid.is_none() {
|
||||
item.uid = Some(help::get_uid("d").into());
|
||||
needs_save = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-save after migration to persist the new IndexMap format
|
||||
if needs_save {
|
||||
logging!(info, Type::Config, "Auto-saving profiles after migration");
|
||||
if let Err(err) = profiles.save_file().await {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
"Failed to auto-save migrated profiles: {}",
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
profiles
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -95,14 +106,14 @@ impl IProfiles {
|
||||
/// 只修改current,valid和chain
|
||||
pub fn patch_config(&mut self, patch: &Self) {
|
||||
if self.items.is_none() {
|
||||
self.items = Some(vec![]);
|
||||
self.items = Some(IndexMap::new());
|
||||
}
|
||||
|
||||
if let Some(current) = &patch.current
|
||||
&& let Some(items) = self.items.as_ref()
|
||||
{
|
||||
let some_uid = Some(current);
|
||||
if items.iter().any(|e| e.uid.as_ref() == some_uid) {
|
||||
if items.iter().any(|e| e.1.uid.as_ref() == some_uid) {
|
||||
self.current = some_uid.cloned();
|
||||
}
|
||||
}
|
||||
@@ -113,34 +124,26 @@ impl IProfiles {
|
||||
}
|
||||
|
||||
/// get items ref
|
||||
pub const fn get_items(&self) -> Option<&Vec<PrfItem>> {
|
||||
pub const fn get_items(&self) -> Option<&IndexMap<String, PrfItem>> {
|
||||
self.items.as_ref()
|
||||
}
|
||||
|
||||
/// find the item by the uid
|
||||
pub fn get_item(&self, uid: impl AsRef<str>) -> Result<&PrfItem> {
|
||||
let uid_str = uid.as_ref();
|
||||
|
||||
if let Some(items) = self.items.as_ref() {
|
||||
for each in items.iter() {
|
||||
if let Some(uid_val) = &each.uid
|
||||
&& uid_val.as_str() == uid_str
|
||||
{
|
||||
return Ok(each);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail!("failed to get the profile item \"uid:{}\"", uid_str);
|
||||
self.items
|
||||
.as_ref()
|
||||
.and_then(|items| items.get(uid.as_ref()))
|
||||
.ok_or_else(|| {
|
||||
let uid_str = uid.as_ref();
|
||||
anyhow::anyhow!("failed to get the profile item \"uid:{}\"", uid_str)
|
||||
})
|
||||
}
|
||||
|
||||
// TODO 或许可以优化掉 clone,或者和 get_item 合并
|
||||
pub fn get_item_arc(&self, uid: &str) -> Option<Arc<PrfItem>> {
|
||||
self.items.as_ref().and_then(|items| {
|
||||
items
|
||||
.iter()
|
||||
.find(|it| it.uid.as_deref() == Some(uid))
|
||||
.map(|it| Arc::new(it.clone()))
|
||||
})
|
||||
self.items
|
||||
.as_ref()
|
||||
.and_then(|items| items.get(uid).cloned().map(Arc::new))
|
||||
}
|
||||
|
||||
/// append new item
|
||||
@@ -176,11 +179,11 @@ impl IProfiles {
|
||||
}
|
||||
|
||||
if self.items.is_none() {
|
||||
self.items = Some(vec![]);
|
||||
self.items = Some(IndexMap::new());
|
||||
}
|
||||
|
||||
if let Some(items) = self.items.as_mut() {
|
||||
items.push(item.to_owned());
|
||||
items.insert(item.uid.clone().unwrap_or_default(), item.to_owned());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -189,24 +192,27 @@ impl IProfiles {
|
||||
/// reorder items
|
||||
pub async fn reorder(&mut self, active_id: &String, over_id: &String) -> Result<()> {
|
||||
let mut items = self.items.take().unwrap_or_default();
|
||||
let mut old_index = None;
|
||||
let mut new_index = None;
|
||||
let mut old_key = None;
|
||||
let mut new_key = None;
|
||||
|
||||
for (i, _) in items.iter().enumerate() {
|
||||
if items[i].uid.as_ref() == Some(active_id) {
|
||||
old_index = Some(i);
|
||||
for (key, item) in items.iter() {
|
||||
if item.uid.as_ref() == Some(active_id) {
|
||||
old_key = Some(key.clone());
|
||||
}
|
||||
if items[i].uid.as_ref() == Some(over_id) {
|
||||
new_index = Some(i);
|
||||
if item.uid.as_ref() == Some(over_id) {
|
||||
new_key = Some(key.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let (old_idx, new_idx) = match (old_index, new_index) {
|
||||
let (old_key, new_key) = match (old_key, new_key) {
|
||||
(Some(old), Some(new)) => (old, new),
|
||||
_ => return Ok(()),
|
||||
};
|
||||
let item = items.remove(old_idx);
|
||||
items.insert(new_idx, item);
|
||||
|
||||
if let Some(item) = items.swap_remove(&old_key) {
|
||||
items.insert(new_key, item);
|
||||
}
|
||||
|
||||
self.items = Some(items);
|
||||
self.save_file().await
|
||||
}
|
||||
@@ -215,21 +221,19 @@ impl IProfiles {
|
||||
pub async fn patch_item(&mut self, uid: &String, item: &PrfItem) -> Result<()> {
|
||||
let mut items = self.items.take().unwrap_or_default();
|
||||
|
||||
for each in items.iter_mut() {
|
||||
if each.uid.as_ref() == Some(uid) {
|
||||
patch!(each, item, itype);
|
||||
patch!(each, item, name);
|
||||
patch!(each, item, desc);
|
||||
patch!(each, item, file);
|
||||
patch!(each, item, url);
|
||||
patch!(each, item, selected);
|
||||
patch!(each, item, extra);
|
||||
patch!(each, item, updated);
|
||||
patch!(each, item, option);
|
||||
if let Some(each) = items.get_mut(uid) {
|
||||
patch!(each, item, itype);
|
||||
patch!(each, item, name);
|
||||
patch!(each, item, desc);
|
||||
patch!(each, item, file);
|
||||
patch!(each, item, url);
|
||||
patch!(each, item, selected);
|
||||
patch!(each, item, extra);
|
||||
patch!(each, item, updated);
|
||||
patch!(each, item, option);
|
||||
|
||||
self.items = Some(items);
|
||||
return self.save_file().await;
|
||||
}
|
||||
self.items = Some(items);
|
||||
return self.save_file().await;
|
||||
}
|
||||
|
||||
self.items = Some(items);
|
||||
@@ -238,46 +242,29 @@ impl IProfiles {
|
||||
|
||||
/// be used to update the remote item
|
||||
/// only patch `updated` `extra` `file_data`
|
||||
pub async fn update_item(&mut self, uid: &String, item: &mut PrfItem) -> Result<()> {
|
||||
if self.items.is_none() {
|
||||
self.items = Some(vec![]);
|
||||
}
|
||||
pub async fn update_item(&mut self, uid: &String, item: &PrfItem) -> Result<()> {
|
||||
let items = self.items.get_or_insert_with(IndexMap::new);
|
||||
|
||||
// find the item
|
||||
let _ = self.get_item(uid)?;
|
||||
let profile_item = items
|
||||
.get_mut(uid)
|
||||
.ok_or_else(|| anyhow::anyhow!("failed to find the profile item \"uid:{}\"", uid))?;
|
||||
|
||||
if let Some(items) = self.items.as_mut() {
|
||||
let some_uid = Some(uid.clone());
|
||||
profile_item.extra = item.extra;
|
||||
profile_item.updated = item.updated;
|
||||
profile_item.home = item.home.clone();
|
||||
profile_item.option = PrfOption::merge(profile_item.option.as_ref(), item.option.as_ref());
|
||||
|
||||
for each in items.iter_mut() {
|
||||
if each.uid == some_uid {
|
||||
each.extra = item.extra;
|
||||
each.updated = item.updated;
|
||||
each.home = item.home.to_owned();
|
||||
each.option = PrfOption::merge(each.option.as_ref(), item.option.as_ref());
|
||||
// save the file data
|
||||
// move the field value after save
|
||||
if let Some(file_data) = item.file_data.take() {
|
||||
let file = each.file.take();
|
||||
let file = file.unwrap_or_else(|| {
|
||||
item.file
|
||||
.take()
|
||||
.unwrap_or_else(|| format!("{}.yaml", &uid).into())
|
||||
});
|
||||
|
||||
// the file must exists
|
||||
each.file = Some(file.clone());
|
||||
|
||||
let path = dirs::app_profiles_dir()?.join(file.as_str());
|
||||
|
||||
fs::write(&path, file_data.as_bytes())
|
||||
.await
|
||||
.with_context(|| format!("failed to write to file \"{file}\""))?;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(file_data) = &item.file_data {
|
||||
let file_name = if let Some(f) = profile_item.file.as_ref() {
|
||||
f.clone()
|
||||
} else {
|
||||
String::from(format!("{}.yaml", uid))
|
||||
};
|
||||
profile_item.file = Some(file_name.clone());
|
||||
let path = dirs::app_profiles_dir()?.join(file_name.as_str());
|
||||
fs::write(&path, file_data.as_bytes())
|
||||
.await
|
||||
.with_context(|| format!("failed to write to file \"{}\"", file_name))?;
|
||||
}
|
||||
|
||||
self.save_file().await
|
||||
@@ -286,86 +273,81 @@ impl IProfiles {
|
||||
/// delete item
|
||||
/// if delete the current then return true
|
||||
pub async fn delete_item(&mut self, uid: &String) -> Result<bool> {
|
||||
let current = self.current.as_ref().unwrap_or(uid);
|
||||
let current = current.clone();
|
||||
let item = self.get_item(uid)?;
|
||||
let merge_uid = item.option.as_ref().and_then(|e| e.merge.clone());
|
||||
let script_uid = item.option.as_ref().and_then(|e| e.script.clone());
|
||||
let rules_uid = item.option.as_ref().and_then(|e| e.rules.clone());
|
||||
let proxies_uid = item.option.as_ref().and_then(|e| e.proxies.clone());
|
||||
let groups_uid = item.option.as_ref().and_then(|e| e.groups.clone());
|
||||
let mut items = self.items.take().unwrap_or_default();
|
||||
let is_current_uid = self.current.as_ref() == Some(uid);
|
||||
|
||||
// remove the main item (if exists) and delete its file
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, Some(uid.clone())) {
|
||||
let item = match self.take_item_by_uid(Some(uid)) {
|
||||
Some(it) => it,
|
||||
None => return Ok(false),
|
||||
};
|
||||
|
||||
let ext_uids = [
|
||||
item.option.as_ref().and_then(|o| o.merge.as_ref()),
|
||||
item.option.as_ref().and_then(|o| o.script.as_ref()),
|
||||
item.option.as_ref().and_then(|o| o.rules.as_ref()),
|
||||
item.option.as_ref().and_then(|o| o.proxies.as_ref()),
|
||||
item.option.as_ref().and_then(|o| o.groups.as_ref()),
|
||||
];
|
||||
|
||||
if let Some(file) = item.file.as_ref() {
|
||||
let _ = dirs::app_profiles_dir()?
|
||||
.join(file.as_str())
|
||||
.remove_if_exists()
|
||||
.await;
|
||||
}
|
||||
|
||||
// remove related extension items (merge, script, rules, proxies, groups)
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, merge_uid.clone()) {
|
||||
let _ = dirs::app_profiles_dir()?
|
||||
.join(file.as_str())
|
||||
.remove_if_exists()
|
||||
.await;
|
||||
}
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, script_uid.clone()) {
|
||||
let _ = dirs::app_profiles_dir()?
|
||||
.join(file.as_str())
|
||||
.remove_if_exists()
|
||||
.await;
|
||||
}
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, rules_uid.clone()) {
|
||||
let _ = dirs::app_profiles_dir()?
|
||||
.join(file.as_str())
|
||||
.remove_if_exists()
|
||||
.await;
|
||||
}
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, proxies_uid.clone()) {
|
||||
let _ = dirs::app_profiles_dir()?
|
||||
.join(file.as_str())
|
||||
.remove_if_exists()
|
||||
.await;
|
||||
}
|
||||
if let Some(file) = Self::take_item_file_by_uid(&mut items, groups_uid.clone()) {
|
||||
let _ = dirs::app_profiles_dir()?
|
||||
.join(file.as_str())
|
||||
.remove_if_exists()
|
||||
.await;
|
||||
}
|
||||
// delete the original uid
|
||||
if current == *uid {
|
||||
self.current = None;
|
||||
for item in items.iter() {
|
||||
if item.itype == Some("remote".into()) || item.itype == Some("local".into()) {
|
||||
self.current = item.uid.clone();
|
||||
break;
|
||||
}
|
||||
for ext_uid in ext_uids.iter().flatten() {
|
||||
if let Some(ext_item) = self.take_item_by_uid(Some(*ext_uid))
|
||||
&& let Some(file) = ext_item.file
|
||||
{
|
||||
let _ = dirs::app_profiles_dir()?
|
||||
.join(file.as_str())
|
||||
.remove_if_exists()
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
self.items = Some(items);
|
||||
if is_current_uid {
|
||||
self.current = self.items.as_ref().and_then(|items| {
|
||||
items.iter().find_map(|(_, i)| {
|
||||
let itype = i.itype.as_deref()?;
|
||||
if itype == "remote" || itype == "local" {
|
||||
i.uid.clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
self.save_file().await?;
|
||||
Ok(current == *uid)
|
||||
Ok(is_current_uid)
|
||||
}
|
||||
|
||||
/// 获取current指向的订阅内容
|
||||
pub async fn current_mapping(&self) -> Result<Mapping> {
|
||||
match (self.current.as_ref(), self.items.as_ref()) {
|
||||
(Some(current), Some(items)) => {
|
||||
if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) {
|
||||
let file_path = match item.file.as_ref() {
|
||||
Some(file) => dirs::app_profiles_dir()?.join(file.as_str()),
|
||||
None => bail!("failed to get the file field"),
|
||||
};
|
||||
return help::read_mapping(&file_path).await;
|
||||
}
|
||||
bail!("failed to find the current profile \"uid:{current}\"");
|
||||
}
|
||||
_ => Ok(Mapping::new()),
|
||||
}
|
||||
let current_uid = match &self.current {
|
||||
Some(uid) => uid,
|
||||
None => return Ok(Mapping::new()),
|
||||
};
|
||||
|
||||
let items = match &self.items {
|
||||
Some(map) => map,
|
||||
None => return Ok(Mapping::new()),
|
||||
};
|
||||
|
||||
let item = items.get(current_uid).ok_or_else(|| {
|
||||
anyhow::anyhow!("failed to find the current profile \"uid:{}\"", current_uid)
|
||||
})?;
|
||||
|
||||
let file = item.file.as_ref().ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"failed to get the file field for profile \"uid:{}\"",
|
||||
current_uid
|
||||
)
|
||||
})?;
|
||||
|
||||
let path = dirs::app_profiles_dir()?.join(file.as_str());
|
||||
help::read_mapping(&path).await
|
||||
}
|
||||
|
||||
/// 判断profile是否是current指向的
|
||||
@@ -377,19 +359,16 @@ impl IProfiles {
|
||||
pub fn profiles_preview(&self) -> Option<Vec<IProfilePreview<'_>>> {
|
||||
self.items.as_ref().map(|items| {
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
if let (Some(uid), Some(name)) = (e.uid.as_ref(), e.name.as_ref()) {
|
||||
let is_current = self.is_current_profile_index(uid);
|
||||
let preview = IProfilePreview {
|
||||
uid,
|
||||
name,
|
||||
is_current,
|
||||
};
|
||||
Some(preview)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.values()
|
||||
.filter_map(|item| {
|
||||
let uid = item.uid.as_ref()?;
|
||||
let name = item.name.as_ref()?;
|
||||
let is_current = self.current.as_ref() == Some(uid);
|
||||
Some(IProfilePreview {
|
||||
uid,
|
||||
name,
|
||||
is_current,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
@@ -397,14 +376,10 @@ impl IProfiles {
|
||||
|
||||
/// 通过 uid 获取名称
|
||||
pub fn get_name_by_uid(&self, uid: &String) -> Option<&String> {
|
||||
if let Some(items) = &self.items {
|
||||
for item in items {
|
||||
if item.uid.as_ref() == Some(uid) {
|
||||
return item.name.as_ref();
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
self.items
|
||||
.as_ref()
|
||||
.and_then(|items| items.get(uid))
|
||||
.and_then(|item| item.name.as_ref())
|
||||
}
|
||||
|
||||
/// 以 app 中的 profile 列表为准,删除不再需要的文件
|
||||
@@ -497,12 +472,11 @@ impl IProfiles {
|
||||
protected_files
|
||||
}
|
||||
|
||||
/// 获取所有 active profile 关联的文件名
|
||||
fn get_all_active_files(&self) -> HashSet<&str> {
|
||||
let mut active_files: HashSet<&str> = HashSet::new();
|
||||
|
||||
if let Some(items) = &self.items {
|
||||
for item in items {
|
||||
for item in items.values() {
|
||||
// 收集所有类型 profile 的文件
|
||||
if let Some(file) = &item.file {
|
||||
active_files.insert(file);
|
||||
@@ -651,3 +625,63 @@ pub async fn profiles_draft_update_item_safe(index: &String, item: &mut PrfItem)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
fn deserialize_items<'de, D>(deserializer: D) -> Result<Option<IndexMap<String, PrfItem>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum ItemsFormat {
|
||||
Map(IndexMap<String, PrfItem>),
|
||||
Vec(Vec<PrfItem>),
|
||||
}
|
||||
|
||||
let value = Option::<ItemsFormat>::deserialize(deserializer)?;
|
||||
|
||||
match value {
|
||||
None => Ok(None),
|
||||
Some(ItemsFormat::Map(map)) => Ok(Some(map)),
|
||||
Some(ItemsFormat::Vec(vec)) => {
|
||||
let mut map = IndexMap::new();
|
||||
for item in vec {
|
||||
if let Some(uid) = &item.uid {
|
||||
map.insert(uid.clone(), item);
|
||||
} else {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Config,
|
||||
"Skipping profile item without uid during migration"
|
||||
);
|
||||
}
|
||||
}
|
||||
logging!(
|
||||
info,
|
||||
Type::Config,
|
||||
"Migrated {} profile items from Vec to IndexMap",
|
||||
map.len()
|
||||
);
|
||||
Ok(Some(map))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for IProfiles {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct IProfilesHelper {
|
||||
current: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_items")]
|
||||
items: Option<IndexMap<String, PrfItem>>,
|
||||
}
|
||||
|
||||
let helper = IProfilesHelper::deserialize(deserializer)?;
|
||||
Ok(Self {
|
||||
current: helper.current,
|
||||
items: helper.items,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,15 +104,15 @@ impl Timer {
|
||||
let profiles_to_update =
|
||||
if let Some(items) = Config::profiles().await.latest_arc().get_items() {
|
||||
items
|
||||
.iter()
|
||||
.values()
|
||||
.filter_map(|item| {
|
||||
let allow_auto_update =
|
||||
item.option.as_ref()?.allow_auto_update.unwrap_or_default();
|
||||
let option = item.option.as_ref()?;
|
||||
let allow_auto_update = option.allow_auto_update.unwrap_or_default();
|
||||
if !allow_auto_update {
|
||||
return None;
|
||||
}
|
||||
|
||||
let interval = item.option.as_ref()?.update_interval? as i64;
|
||||
let interval = option.update_interval? as i64;
|
||||
let updated = item.updated? as i64;
|
||||
let uid = item.uid.as_ref()?;
|
||||
|
||||
@@ -271,12 +271,12 @@ impl Timer {
|
||||
let mut new_map = HashMap::new();
|
||||
|
||||
if let Some(items) = Config::profiles().await.latest_arc().get_items() {
|
||||
for item in items.iter() {
|
||||
for item in items.values() {
|
||||
if let Some(option) = item.option.as_ref()
|
||||
&& let Some(allow_auto_update) = option.allow_auto_update
|
||||
&& let (Some(interval), Some(uid)) = (option.update_interval, &item.uid)
|
||||
&& allow_auto_update
|
||||
&& option.allow_auto_update.unwrap_or_default()
|
||||
&& let Some(interval) = option.update_interval
|
||||
&& interval > 0
|
||||
&& let Some(uid) = &item.uid
|
||||
{
|
||||
logging!(
|
||||
debug,
|
||||
@@ -422,23 +422,17 @@ impl Timer {
|
||||
}
|
||||
};
|
||||
|
||||
// Get the profile updated timestamp - now safe to await
|
||||
let items = {
|
||||
let profiles = Config::profiles().await;
|
||||
let profiles_guard = profiles.latest_arc();
|
||||
match profiles_guard.get_items() {
|
||||
Some(i) => i.clone(),
|
||||
None => {
|
||||
logging!(warn, Type::Timer, "获取配置列表失败");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let profile = match items.iter().find(|item| item.uid.as_deref() == Some(uid)) {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
logging!(warn, Type::Timer, "找不到对应的配置,uid={}", uid);
|
||||
let profiles = Config::profiles().await.latest_arc();
|
||||
let profile = match profiles.get_item(uid) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::Timer,
|
||||
"找不到对应的配置,uid={}, 错误: {}",
|
||||
uid,
|
||||
e
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,11 +2,11 @@ import useSWR, { mutate } from "swr";
|
||||
import { selectNodeForGroup } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import {
|
||||
calcuProxies,
|
||||
getProfiles,
|
||||
patchProfile,
|
||||
patchProfilesConfig,
|
||||
} from "@/services/cmds";
|
||||
import { calcuProxies } from "@/services/cmds";
|
||||
|
||||
export const useProfiles = () => {
|
||||
const {
|
||||
@@ -83,9 +83,10 @@ export const useProfiles = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const current = profileData.items?.find(
|
||||
(e) => e && e.uid === profileData.current,
|
||||
);
|
||||
const current =
|
||||
profileData.current && profileData.items
|
||||
? profileData.items[profileData.current]
|
||||
: undefined;
|
||||
|
||||
if (!current) {
|
||||
console.log("[ActivateSelected] 未找到当前profile配置");
|
||||
@@ -197,7 +198,7 @@ export const useProfiles = () => {
|
||||
|
||||
return {
|
||||
profiles,
|
||||
current: profiles?.items?.find((p) => p && p.uid === profiles.current),
|
||||
current: profiles?.items?.[profiles.current || ""],
|
||||
activateSelected,
|
||||
patchProfiles,
|
||||
patchCurrent,
|
||||
|
||||
@@ -263,11 +263,11 @@ const ProfilePage = () => {
|
||||
|
||||
// distinguish type
|
||||
const profileItems = useMemo(() => {
|
||||
const items = profiles.items || [];
|
||||
const items = profiles.items || {};
|
||||
|
||||
const type1 = ["local", "remote"];
|
||||
|
||||
return items.filter((i) => i && type1.includes(i.type!));
|
||||
return Object.values(items).filter((i) => i && type1.includes(i.type!));
|
||||
}, [profiles]);
|
||||
|
||||
const currentActivatings = () => {
|
||||
|
||||
2
src/types/types.d.ts
vendored
2
src/types/types.d.ts
vendored
@@ -280,7 +280,7 @@ interface IProfileOption {
|
||||
|
||||
interface IProfilesConfig {
|
||||
current?: string;
|
||||
items?: IProfileItem[];
|
||||
items?: Record<string, IProfileItem>;
|
||||
}
|
||||
|
||||
interface IVergeTestItem {
|
||||
|
||||
Reference in New Issue
Block a user