Compare commits

...

2 Commits

Author SHA1 Message Date
Tunglies
0952dc95b3 refactor: clean up unused imports in profiles module 2025-11-23 20:06:06 +08:00
Tunglies
a58999d37e refactor: profiles use IndexMap 2025-11-22 19:34:36 +08:00
8 changed files with 283 additions and 249 deletions

57
Cargo.lock generated
View File

@@ -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",

View File

@@ -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" }

View File

@@ -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"] }
warp = { version = "0.4.2", features = ["server"] }
open = "5.3.3"
dunce = "1.0.5"

View File

@@ -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 {
/// 只修改currentvalid和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,29 @@ 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);
let old_index = items.get_index_of(&old_key);
let new_index = items.get_index_of(&new_key);
if let (Some(old_idx), Some(new_idx)) = (old_index, new_index) {
items.move_index(old_idx, new_idx);
}
self.items = Some(items);
self.save_file().await
}
@@ -215,21 +223,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 +244,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 +275,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 +361,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 +378,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 +474,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 +627,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,
})
}
}

View File

@@ -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;
}
};

View File

@@ -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,

View File

@@ -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 = () => {

View File

@@ -280,7 +280,7 @@ interface IProfileOption {
interface IProfilesConfig {
current?: string;
items?: IProfileItem[];
items?: Record<string, IProfileItem>;
}
interface IVergeTestItem {