mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-28 07:14:40 +08:00
Revert "perf: improve config processing (#6091)"
This reverts commit bf189bb144.
This commit is contained in:
@@ -117,7 +117,7 @@ pub async fn import_profile(url: std::string::String, option: Option<PrfOption>)
|
|||||||
pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
|
pub async fn reorder_profile(active_id: String, over_id: String) -> CmdResult {
|
||||||
match profiles_reorder_safe(&active_id, &over_id).await {
|
match profiles_reorder_safe(&active_id, &over_id).await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
logging!(debug, Type::Cmd, "重新排序配置文件");
|
logging!(info, Type::Cmd, "重新排序配置文件");
|
||||||
Config::profiles().await.apply();
|
Config::profiles().await.apply();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,13 +60,13 @@ pub struct PrfItem {
|
|||||||
pub file_data: Option<String>,
|
pub file_data: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct PrfSelected {
|
pub struct PrfSelected {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub now: Option<String>,
|
pub now: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Copy, Deserialize, Serialize)]
|
||||||
pub struct PrfExtra {
|
pub struct PrfExtra {
|
||||||
pub upload: u64,
|
pub upload: u64,
|
||||||
pub download: u64,
|
pub download: u64,
|
||||||
@@ -124,22 +124,25 @@ pub struct PrfOption {
|
|||||||
impl PrfOption {
|
impl PrfOption {
|
||||||
pub fn merge(one: Option<&Self>, other: Option<&Self>) -> Option<Self> {
|
pub fn merge(one: Option<&Self>, other: Option<&Self>) -> Option<Self> {
|
||||||
match (one, other) {
|
match (one, other) {
|
||||||
(Some(a), Some(b)) => Some(Self {
|
(Some(a_ref), Some(b_ref)) => {
|
||||||
user_agent: b.user_agent.as_ref().or(a.user_agent.as_ref()).cloned(),
|
let mut result = a_ref.clone();
|
||||||
with_proxy: b.with_proxy.or(a.with_proxy),
|
result.user_agent = b_ref.user_agent.clone().or(result.user_agent);
|
||||||
self_proxy: b.self_proxy.or(a.self_proxy),
|
result.with_proxy = b_ref.with_proxy.or(result.with_proxy);
|
||||||
danger_accept_invalid_certs: b.danger_accept_invalid_certs.or(a.danger_accept_invalid_certs),
|
result.self_proxy = b_ref.self_proxy.or(result.self_proxy);
|
||||||
allow_auto_update: b.allow_auto_update.or(a.allow_auto_update),
|
result.danger_accept_invalid_certs =
|
||||||
update_interval: b.update_interval.or(a.update_interval),
|
b_ref.danger_accept_invalid_certs.or(result.danger_accept_invalid_certs);
|
||||||
merge: b.merge.as_ref().or(a.merge.as_ref()).cloned(),
|
result.allow_auto_update = b_ref.allow_auto_update.or(result.allow_auto_update);
|
||||||
script: b.script.as_ref().or(a.script.as_ref()).cloned(),
|
result.update_interval = b_ref.update_interval.or(result.update_interval);
|
||||||
rules: b.rules.as_ref().or(a.rules.as_ref()).cloned(),
|
result.merge = b_ref.merge.clone().or(result.merge);
|
||||||
proxies: b.proxies.as_ref().or(a.proxies.as_ref()).cloned(),
|
result.script = b_ref.script.clone().or(result.script);
|
||||||
groups: b.groups.as_ref().or(a.groups.as_ref()).cloned(),
|
result.rules = b_ref.rules.clone().or(result.rules);
|
||||||
timeout_seconds: b.timeout_seconds.or(a.timeout_seconds),
|
result.proxies = b_ref.proxies.clone().or(result.proxies);
|
||||||
}),
|
result.groups = b_ref.groups.clone().or(result.groups);
|
||||||
(Some(a), None) => Some(a.clone()),
|
result.timeout_seconds = b_ref.timeout_seconds.or(result.timeout_seconds);
|
||||||
(None, Some(b)) => Some(b.clone()),
|
Some(result)
|
||||||
|
}
|
||||||
|
(Some(a_ref), None) => Some(a_ref.clone()),
|
||||||
|
(None, Some(b_ref)) => Some(b_ref.clone()),
|
||||||
(None, None) => None,
|
(None, None) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,12 @@ use crate::utils::{
|
|||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result, bail};
|
use anyhow::{Context as _, Result, bail};
|
||||||
use clash_verge_logging::{Type, logging};
|
use clash_verge_logging::{Type, logging};
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use regex::Regex;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_yaml_ng::Mapping;
|
use serde_yaml_ng::Mapping;
|
||||||
use smartstring::alias::String;
|
use smartstring::alias::String;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashSet;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
static PROFILE_FILE_RE: OnceCell<Regex> = OnceCell::new();
|
|
||||||
|
|
||||||
/// Define the `profiles.yaml` schema
|
/// Define the `profiles.yaml` schema
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct IProfiles {
|
pub struct IProfiles {
|
||||||
@@ -41,15 +37,23 @@ pub struct CleanupResult {
|
|||||||
|
|
||||||
macro_rules! patch {
|
macro_rules! patch {
|
||||||
($lv: expr, $rv: expr, $key: tt) => {
|
($lv: expr, $rv: expr, $key: tt) => {
|
||||||
if let Some(ref val) = $rv.$key {
|
if ($rv.$key).is_some() {
|
||||||
if Some(val) != $lv.$key.as_ref() {
|
$lv.$key = $rv.$key.to_owned();
|
||||||
$lv.$key = Some(val.to_owned());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IProfiles {
|
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
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn new() -> Self {
|
pub async fn new() -> Self {
|
||||||
let path = match dirs::profiles_path() {
|
let path = match dirs::profiles_path() {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
@@ -59,22 +63,21 @@ impl IProfiles {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut profiles = match help::read_yaml::<Self>(&path).await {
|
match help::read_yaml::<Self>(&path).await {
|
||||||
Ok(profiles) => profiles,
|
Ok(mut profiles) => {
|
||||||
|
let items = profiles.items.get_or_insert_with(Vec::new);
|
||||||
|
for item in items.iter_mut() {
|
||||||
|
if item.uid.is_none() {
|
||||||
|
item.uid = Some(help::get_uid("d").into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
profiles
|
||||||
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
logging!(error, Type::Config, "{err}");
|
logging!(error, Type::Config, "{err}");
|
||||||
return Self::default();
|
Self::default()
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let items = profiles.items.get_or_insert_with(Vec::new);
|
|
||||||
for item in items.iter_mut() {
|
|
||||||
if item.uid.is_none() {
|
|
||||||
item.uid = Some(help::get_uid("d").into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn save_file(&self) -> Result<()> {
|
pub async fn save_file(&self) -> Result<()> {
|
||||||
@@ -110,28 +113,38 @@ impl IProfiles {
|
|||||||
pub fn get_item(&self, uid: impl AsRef<str>) -> Result<&PrfItem> {
|
pub fn get_item(&self, uid: impl AsRef<str>) -> Result<&PrfItem> {
|
||||||
let uid_str = uid.as_ref();
|
let uid_str = uid.as_ref();
|
||||||
|
|
||||||
self.items
|
if let Some(items) = self.items.as_ref() {
|
||||||
.as_ref()
|
for each in items.iter() {
|
||||||
.ok_or_else(|| anyhow::anyhow!("no profile items found"))?
|
if let Some(uid_val) = &each.uid
|
||||||
.iter()
|
&& uid_val.as_str() == uid_str
|
||||||
.find(|each| each.uid.as_ref().is_some_and(|uid_val| uid_val.as_str() == uid_str))
|
{
|
||||||
.ok_or_else(|| anyhow::anyhow!("failed to get the profile item \"uid:{}\"", uid_str))
|
return Ok(each);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("failed to get the profile item \"uid:{}\"", uid_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// append new item
|
/// append new item
|
||||||
/// if the file_data is some
|
/// if the file_data is some
|
||||||
/// then should save the data to file
|
/// then should save the data to file
|
||||||
pub async fn append_item(&mut self, item: &mut PrfItem) -> Result<()> {
|
pub async fn append_item(&mut self, item: &mut PrfItem) -> Result<()> {
|
||||||
anyhow::ensure!(item.uid.is_some(), "the uid should not be null");
|
let uid = &item.uid;
|
||||||
|
if uid.is_none() {
|
||||||
|
bail!("the uid should not be null");
|
||||||
|
}
|
||||||
|
|
||||||
// save the file data
|
// save the file data
|
||||||
// move the field value after save
|
// move the field value after save
|
||||||
if let Some(file_data) = item.file_data.take() {
|
if let Some(file_data) = item.file_data.take() {
|
||||||
anyhow::ensure!(item.file.is_some(), "the file should not be null");
|
if item.file.is_none() {
|
||||||
|
bail!("the file should not be null");
|
||||||
|
}
|
||||||
|
|
||||||
let file = item
|
let file = item
|
||||||
.file
|
.file
|
||||||
.as_ref()
|
.clone()
|
||||||
.ok_or_else(|| anyhow::anyhow!("file field is required when file_data is provided"))?;
|
.ok_or_else(|| anyhow::anyhow!("file field is required when file_data is provided"))?;
|
||||||
let path = dirs::app_profiles_dir()?.join(file.as_str());
|
let path = dirs::app_profiles_dir()?.join(file.as_str());
|
||||||
|
|
||||||
@@ -140,116 +153,111 @@ impl IProfiles {
|
|||||||
.with_context(|| format!("failed to write to file \"{file}\""))?;
|
.with_context(|| format!("failed to write to file \"{file}\""))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.current.is_none()
|
if self.current.is_none() && (item.itype == Some("remote".into()) || item.itype == Some("local".into())) {
|
||||||
&& let Some(t) = item.itype.as_deref()
|
self.current = uid.to_owned();
|
||||||
&& (t == "remote" || t == "local")
|
|
||||||
{
|
|
||||||
self.current = item.uid.to_owned();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.items.get_or_insert_default().push(std::mem::take(item));
|
if self.items.is_none() {
|
||||||
|
self.items = Some(vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(items) = self.items.as_mut() {
|
||||||
|
items.push(item.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// reorder items
|
/// reorder items
|
||||||
pub async fn reorder(&mut self, active_id: &str, over_id: &str) -> Result<()> {
|
pub async fn reorder(&mut self, active_id: &String, over_id: &String) -> Result<()> {
|
||||||
if active_id == over_id {
|
let mut items = self.items.take().unwrap_or_default();
|
||||||
return Ok(());
|
let mut old_index = None;
|
||||||
|
let mut new_index = None;
|
||||||
|
|
||||||
|
for (i, _) in items.iter().enumerate() {
|
||||||
|
if items[i].uid.as_ref() == Some(active_id) {
|
||||||
|
old_index = Some(i);
|
||||||
|
}
|
||||||
|
if items[i].uid.as_ref() == Some(over_id) {
|
||||||
|
new_index = Some(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(items) = self.items.as_mut() else {
|
let (old_idx, new_idx) = match (old_index, new_index) {
|
||||||
return Ok(());
|
(Some(old), Some(new)) => (old, new),
|
||||||
|
_ => return Ok(()),
|
||||||
};
|
};
|
||||||
|
let item = items.remove(old_idx);
|
||||||
let mut old_idx = None;
|
items.insert(new_idx, item);
|
||||||
let mut new_idx = None;
|
self.items = Some(items);
|
||||||
|
self.save_file().await
|
||||||
for (i, item) in items.iter().enumerate() {
|
|
||||||
if let Some(uid) = item.uid.as_ref() {
|
|
||||||
if uid == active_id {
|
|
||||||
old_idx = Some(i);
|
|
||||||
}
|
|
||||||
if uid == over_id {
|
|
||||||
new_idx = Some(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if old_idx.is_some() && new_idx.is_some() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (Some(old), Some(new)) = (old_idx, new_idx) {
|
|
||||||
if old < new {
|
|
||||||
items[old..=new].rotate_left(1);
|
|
||||||
} else {
|
|
||||||
items[new..=old].rotate_right(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.save_file().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// update the item value
|
/// update the item value
|
||||||
pub async fn patch_item(&mut self, uid: &String, item: &PrfItem) -> Result<()> {
|
pub async fn patch_item(&mut self, uid: &String, item: &PrfItem) -> Result<()> {
|
||||||
let items = self
|
let mut items = self.items.take().unwrap_or_default();
|
||||||
.items
|
|
||||||
.as_mut()
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("no profile items found"))?;
|
|
||||||
|
|
||||||
let target = items.iter_mut().find(|each| each.uid.as_ref() == Some(uid));
|
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) = target {
|
self.items = Some(items);
|
||||||
patch!(each, item, itype);
|
return self.save_file().await;
|
||||||
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);
|
|
||||||
|
|
||||||
return self.save_file().await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.items = Some(items);
|
||||||
bail!("failed to find the profile item \"uid:{uid}\"")
|
bail!("failed to find the profile item \"uid:{uid}\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// be used to update the remote item
|
/// be used to update the remote item
|
||||||
/// only patch `updated` `extra` `file_data`
|
/// only patch `updated` `extra` `file_data`
|
||||||
pub async fn update_item(&mut self, uid: &String, item: &mut PrfItem) -> Result<()> {
|
pub async fn update_item(&mut self, uid: &String, item: &mut PrfItem) -> Result<()> {
|
||||||
let target = self
|
if self.items.is_none() {
|
||||||
.items
|
self.items = Some(vec![]);
|
||||||
.get_or_insert_default()
|
}
|
||||||
.iter_mut()
|
|
||||||
.find(|each| each.uid.as_ref() == Some(uid))
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Item not found"))?;
|
|
||||||
|
|
||||||
target.extra = item.extra;
|
// find the item
|
||||||
target.updated = item.updated;
|
let _ = self.get_item(uid)?;
|
||||||
target.home = std::mem::take(&mut item.home);
|
|
||||||
target.option = PrfOption::merge(target.option.as_ref(), item.option.as_ref());
|
|
||||||
|
|
||||||
let Some(file_data) = item.file_data.take() else {
|
if let Some(items) = self.items.as_mut() {
|
||||||
return self.save_file().await;
|
let some_uid = Some(uid.clone());
|
||||||
};
|
|
||||||
|
|
||||||
let file = target
|
for each in items.iter_mut() {
|
||||||
.file
|
if each.uid == some_uid {
|
||||||
.take()
|
each.extra = item.extra;
|
||||||
.or_else(|| item.file.take())
|
each.updated = item.updated;
|
||||||
.unwrap_or_else(|| format!("{}.yaml", uid).into());
|
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()));
|
||||||
|
|
||||||
let path = dirs::app_profiles_dir()?.join(file.as_str());
|
// the file must exists
|
||||||
|
each.file = Some(file.clone());
|
||||||
|
|
||||||
fs::write(&path, file_data.as_bytes())
|
let path = dirs::app_profiles_dir()?.join(file.as_str());
|
||||||
.await
|
|
||||||
.with_context(|| format!("failed to write to file \"{file}\""))?;
|
|
||||||
|
|
||||||
target.file = Some(file);
|
fs::write(&path, file_data.as_bytes())
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to write to file \"{file}\""))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.save_file().await
|
self.save_file().await
|
||||||
}
|
}
|
||||||
@@ -257,82 +265,68 @@ impl IProfiles {
|
|||||||
/// delete item
|
/// delete item
|
||||||
/// if delete the current then return true
|
/// if delete the current then return true
|
||||||
pub async fn delete_item(&mut self, uid: &String) -> Result<bool> {
|
pub async fn delete_item(&mut self, uid: &String) -> Result<bool> {
|
||||||
let uids_to_remove: HashSet<String> = {
|
let current = self.current.as_ref().unwrap_or(uid);
|
||||||
let item = self.get_item(uid)?;
|
let current = current.clone();
|
||||||
let mut set = HashSet::new();
|
let item = self.get_item(uid)?;
|
||||||
set.insert(uid.clone());
|
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());
|
||||||
if let Some(opt) = &item.option {
|
let rules_uid = item.option.as_ref().and_then(|e| e.rules.clone());
|
||||||
if let Some(u) = &opt.merge {
|
let proxies_uid = item.option.as_ref().and_then(|e| e.proxies.clone());
|
||||||
set.insert(u.clone());
|
let groups_uid = item.option.as_ref().and_then(|e| e.groups.clone());
|
||||||
}
|
|
||||||
if let Some(u) = &opt.script {
|
|
||||||
set.insert(u.clone());
|
|
||||||
}
|
|
||||||
if let Some(u) = &opt.rules {
|
|
||||||
set.insert(u.clone());
|
|
||||||
}
|
|
||||||
if let Some(u) = &opt.proxies {
|
|
||||||
set.insert(u.clone());
|
|
||||||
}
|
|
||||||
if let Some(u) = &opt.groups {
|
|
||||||
set.insert(u.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut items = self.items.take().unwrap_or_default();
|
let mut items = self.items.take().unwrap_or_default();
|
||||||
let mut deleted_files = Vec::new();
|
|
||||||
|
|
||||||
items.retain_mut(|item| {
|
// remove the main item (if exists) and delete its file
|
||||||
if let Some(item_uid) = item.uid.as_ref()
|
if let Some(file) = Self::take_item_file_by_uid(&mut items, Some(uid.clone())) {
|
||||||
&& uids_to_remove.contains(item_uid)
|
let _ = dirs::app_profiles_dir()?.join(file.as_str()).remove_if_exists().await;
|
||||||
{
|
}
|
||||||
if let Some(file) = item.file.take() {
|
|
||||||
deleted_files.push(file);
|
// 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;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
true
|
|
||||||
});
|
|
||||||
|
|
||||||
let is_deleting_current = self.current.as_ref() == Some(uid);
|
|
||||||
if is_deleting_current {
|
|
||||||
self.current = items
|
|
||||||
.iter()
|
|
||||||
.find(|i| i.itype.as_deref() == Some("remote") || i.itype.as_deref() == Some("local"))
|
|
||||||
.and_then(|i| i.uid.clone());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.items = Some(items);
|
self.items = Some(items);
|
||||||
|
|
||||||
if let Ok(profile_dir) = dirs::app_profiles_dir() {
|
|
||||||
for file in deleted_files {
|
|
||||||
let _ = profile_dir.join(file.as_str()).remove_if_exists().await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.save_file().await?;
|
self.save_file().await?;
|
||||||
Ok(is_deleting_current)
|
Ok(current == *uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取current指向的订阅内容
|
/// 获取current指向的订阅内容
|
||||||
pub async fn current_mapping(&self) -> Result<Mapping> {
|
pub async fn current_mapping(&self) -> Result<Mapping> {
|
||||||
let (Some(current), Some(items)) = (self.current.as_ref(), self.items.as_ref()) else {
|
match (self.current.as_ref(), self.items.as_ref()) {
|
||||||
return Ok(Mapping::new());
|
(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() {
|
||||||
let Some(target) = items.iter().find(|e| e.uid.as_ref() == Some(current)) else {
|
Some(file) => dirs::app_profiles_dir()?.join(file.as_str()),
|
||||||
bail!("failed to find the current profile \"uid:{current}\"");
|
None => bail!("failed to get the file field"),
|
||||||
};
|
};
|
||||||
|
return help::read_mapping(&file_path).await;
|
||||||
let file = target
|
}
|
||||||
.file
|
bail!("failed to find the current profile \"uid:{current}\"");
|
||||||
.as_ref()
|
}
|
||||||
.ok_or_else(|| anyhow::anyhow!("failed to get the file field"))?;
|
_ => Ok(Mapping::new()),
|
||||||
let file_path = dirs::app_profiles_dir()?.join(file.as_str());
|
}
|
||||||
help::read_mapping(&file_path).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 判断profile是否是current指向的
|
/// 判断profile是否是current指向的
|
||||||
@@ -342,32 +336,32 @@ impl IProfiles {
|
|||||||
|
|
||||||
/// 获取所有的profiles(uid,名称, 是否为 current)
|
/// 获取所有的profiles(uid,名称, 是否为 current)
|
||||||
pub fn profiles_preview(&self) -> Option<Vec<IProfilePreview<'_>>> {
|
pub fn profiles_preview(&self) -> Option<Vec<IProfilePreview<'_>>> {
|
||||||
let items = self.items.as_ref()?;
|
self.items.as_ref().map(|items| {
|
||||||
let current_uid = self.current.as_ref();
|
items
|
||||||
|
.iter()
|
||||||
let previews = items
|
.filter_map(|e| {
|
||||||
.iter()
|
if let (Some(uid), Some(name)) = (e.uid.as_ref(), e.name.as_ref()) {
|
||||||
.filter_map(|e| {
|
let is_current = self.is_current_profile_index(uid);
|
||||||
let uid = e.uid.as_ref()?;
|
let preview = IProfilePreview { uid, name, is_current };
|
||||||
let name = e.name.as_ref()?;
|
Some(preview)
|
||||||
Some(IProfilePreview {
|
} else {
|
||||||
uid,
|
None
|
||||||
name,
|
}
|
||||||
is_current: current_uid == Some(uid),
|
|
||||||
})
|
})
|
||||||
})
|
.collect()
|
||||||
.collect();
|
})
|
||||||
|
|
||||||
Some(previews)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 通过 uid 获取名称
|
/// 通过 uid 获取名称
|
||||||
pub fn get_name_by_uid(&self, uid: &str) -> Option<&String> {
|
pub fn get_name_by_uid(&self, uid: &String) -> Option<&String> {
|
||||||
self.items
|
if let Some(items) = &self.items {
|
||||||
.as_ref()?
|
for item in items {
|
||||||
.iter()
|
if item.uid.as_ref() == Some(uid) {
|
||||||
.find(|item| item.uid.as_deref() == Some(uid))
|
return item.name.as_ref();
|
||||||
.and_then(|item| item.name.as_ref())
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 以 app 中的 profile 列表为准,删除不再需要的文件
|
/// 以 app 中的 profile 列表为准,删除不再需要的文件
|
||||||
@@ -454,39 +448,59 @@ impl IProfiles {
|
|||||||
|
|
||||||
/// 获取所有 active profile 关联的文件名
|
/// 获取所有 active profile 关联的文件名
|
||||||
fn get_all_active_files(&self) -> HashSet<&str> {
|
fn get_all_active_files(&self) -> HashSet<&str> {
|
||||||
let mut active_files = HashSet::new();
|
let mut active_files: HashSet<&str> = HashSet::new();
|
||||||
let items = match &self.items {
|
|
||||||
Some(i) => i,
|
|
||||||
None => return active_files,
|
|
||||||
};
|
|
||||||
|
|
||||||
let item_map: HashMap<Option<&str>, &PrfItem> = items.iter().map(|i| (i.uid.as_deref(), i)).collect();
|
if let Some(items) = &self.items {
|
||||||
|
for item in items {
|
||||||
|
// 收集所有类型 profile 的文件
|
||||||
|
if let Some(file) = &item.file {
|
||||||
|
active_files.insert(file);
|
||||||
|
}
|
||||||
|
|
||||||
for item in items {
|
// 对于主 profile 类型(remote/local),还需要收集其关联的扩展文件
|
||||||
if let Some(f) = &item.file {
|
if let Some(itype) = &item.itype
|
||||||
active_files.insert(f.as_str());
|
&& (itype == "remote" || itype == "local")
|
||||||
}
|
&& let Some(option) = &item.option
|
||||||
|
|
||||||
let Some(opt) = &item.option else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let related = [
|
|
||||||
opt.merge.as_deref(),
|
|
||||||
opt.script.as_deref(),
|
|
||||||
opt.rules.as_deref(),
|
|
||||||
opt.proxies.as_deref(),
|
|
||||||
opt.groups.as_deref(),
|
|
||||||
];
|
|
||||||
|
|
||||||
for r_uid in related.into_iter().flatten() {
|
|
||||||
if let Some(r_item) = item_map.get(&Some(r_uid))
|
|
||||||
&& let Some(f) = &r_item.file
|
|
||||||
{
|
{
|
||||||
active_files.insert(f.as_str());
|
// 收集关联的扩展文件
|
||||||
|
if let Some(merge_uid) = &option.merge
|
||||||
|
&& let Ok(merge_item) = self.get_item(merge_uid)
|
||||||
|
&& let Some(file) = &merge_item.file
|
||||||
|
{
|
||||||
|
active_files.insert(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(script_uid) = &option.script
|
||||||
|
&& let Ok(script_item) = self.get_item(script_uid)
|
||||||
|
&& let Some(file) = &script_item.file
|
||||||
|
{
|
||||||
|
active_files.insert(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rules_uid) = &option.rules
|
||||||
|
&& let Ok(rules_item) = self.get_item(rules_uid)
|
||||||
|
&& let Some(file) = &rules_item.file
|
||||||
|
{
|
||||||
|
active_files.insert(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(proxies_uid) = &option.proxies
|
||||||
|
&& let Ok(proxies_item) = self.get_item(proxies_uid)
|
||||||
|
&& let Some(file) = &proxies_item.file
|
||||||
|
{
|
||||||
|
active_files.insert(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(groups_uid) = &option.groups
|
||||||
|
&& let Ok(groups_item) = self.get_item(groups_uid)
|
||||||
|
&& let Some(file) = &groups_item.file
|
||||||
|
{
|
||||||
|
active_files.insert(file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
active_files
|
active_files
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,10 +515,18 @@ impl IProfiles {
|
|||||||
// p12345678.yaml (proxies)
|
// p12345678.yaml (proxies)
|
||||||
// g12345678.yaml (groups)
|
// g12345678.yaml (groups)
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
let patterns = [
|
||||||
let re = PROFILE_FILE_RE
|
r"^[RL][a-zA-Z0-9]+\.yaml$", // Remote/Local profiles
|
||||||
.get_or_init(|| Regex::new(r"^(?:[RLmprg][a-zA-Z0-9_-]+\.yaml|s[a-zA-Z0-9_-]+\.js)$").unwrap());
|
r"^m[a-zA-Z0-9]+\.yaml$", // Merge files
|
||||||
re.is_match(filename)
|
r"^s[a-zA-Z0-9]+\.js$", // Script files
|
||||||
|
r"^[rpg][a-zA-Z0-9]+\.yaml$", // Rules/Proxies/Groups files
|
||||||
|
];
|
||||||
|
|
||||||
|
patterns.iter().any(|pattern| {
|
||||||
|
regex::Regex::new(pattern)
|
||||||
|
.map(|re| re.is_match(filename))
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ pub struct TimerTask {
|
|||||||
pub last_run: i64, // Timestamp of last execution
|
pub last_run: i64, // Timestamp of last execution
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 一个 Timer 负责轻量, 一个 Timer 负责订阅更新。当前会生产 N(订阅数量) + 1 个定时任务
|
|
||||||
pub struct Timer {
|
pub struct Timer {
|
||||||
/// cron manager
|
/// cron manager
|
||||||
pub delay_timer: Arc<RwLock<DelayTimer>>,
|
pub delay_timer: Arc<RwLock<DelayTimer>>,
|
||||||
|
|||||||
Reference in New Issue
Block a user