mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-22 19:52:14 +08:00
fix(profile): schedule imported profiles for auto-update
This commit is contained in:
+2
-1
@@ -41,11 +41,12 @@
|
||||
- 修复 Windows 管理员模式下启动 TUN 时因等待不可用 Service 导致延迟 30 秒的问题
|
||||
- 修复内核启动慢导致首页上下跳动的问题
|
||||
- 修复 VLESS xhttp 链接解析及 IPv6 地址格式问题
|
||||
- 修复通过普通导入或深链导入订阅后,自动更新任务未及时生效的问题
|
||||
|
||||
<details>
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
|
||||
- **Mihomo(Meta) 内核升级至 v1.19.28**
|
||||
- **Mihomo(Meta) 内核升级至 v1.19.29**
|
||||
- 增加 TrustTunnel、OpenVPN、Tailscale、GostRelay 节点显示支持
|
||||
- 全局扩展脚本增加恢复默认按钮
|
||||
- DNS 添加 fake-ip-range6 可配置项
|
||||
|
||||
@@ -16,8 +16,8 @@ use crate::{
|
||||
feat,
|
||||
utils::{dirs, help},
|
||||
};
|
||||
use clash_verge_draft::SharedDraft;
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use clash_verge_draft::{Draft, SharedDraft};
|
||||
use clash_verge_logging::{Type, logging, logging_error};
|
||||
use scopeguard::defer;
|
||||
use smartstring::alias::String;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
@@ -83,21 +83,18 @@ pub async fn import_profile(url: std::string::String, option: Option<PrfOption>)
|
||||
}
|
||||
};
|
||||
|
||||
match profiles_append_item_safe(item).await {
|
||||
Ok(_) => match profiles_save_file_safe().await {
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Cmd, "[导入订阅] 配置文件保存成功");
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(error, Type::Cmd, "[导入订阅] 保存配置文件失败: {}", e);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
logging!(error, Type::Cmd, "[导入订阅] 保存配置失败: {}", e);
|
||||
return Err(format!("导入订阅失败: {}", e).into());
|
||||
}
|
||||
if let Err(e) = profiles_append_item_safe(item).await {
|
||||
logging!(error, Type::Cmd, "[导入订阅] 保存配置失败: {}", e);
|
||||
return Err(format!("导入订阅失败: {}", e).into());
|
||||
}
|
||||
|
||||
if let Err(e) = profiles_save_file_safe().await {
|
||||
logging!(error, Type::Cmd, "[导入订阅] 保存配置文件失败: {}", e);
|
||||
return Err(format!("导入订阅失败: {}", e).into());
|
||||
}
|
||||
logging!(info, Type::Cmd, "[导入订阅] 配置文件保存成功");
|
||||
logging_error!(Type::Timer, Timer::global().refresh().await);
|
||||
|
||||
if let Some(uid) = &item.uid {
|
||||
logging!(info, Type::Cmd, "[导入订阅] 发送配置变更通知: {}", uid);
|
||||
handle::Handle::notify_profile_changed(uid);
|
||||
@@ -129,6 +126,7 @@ pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResu
|
||||
match profiles_append_item_with_filedata_safe(&item, file_data).await {
|
||||
Ok(_) => {
|
||||
profiles_save_file_safe().await.stringify_err()?;
|
||||
logging_error!(Type::Timer, Timer::global().refresh().await);
|
||||
// 发送配置变更通知
|
||||
if let Some(uid) = &item.uid {
|
||||
logging!(info, Type::Cmd, "[创建订阅] 发送配置变更通知: {}", uid);
|
||||
@@ -211,8 +209,27 @@ async fn restore_previous_profile(prev_profile: &String) -> CmdResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn commit_current_profile(profiles: &Draft<IProfiles>, current: Option<String>) -> anyhow::Result<()> {
|
||||
profiles.discard();
|
||||
let Some(current) = current else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
profiles
|
||||
.with_data_modify(|mut committed| async move {
|
||||
committed.patch_config(&IProfiles {
|
||||
current: Some(current),
|
||||
items: None,
|
||||
});
|
||||
Ok((committed, ()))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn handle_success(current_value: Option<&String>) -> CmdResult<ValidationOutcome> {
|
||||
Config::profiles().await.apply();
|
||||
commit_current_profile(&Config::profiles().await, current_value.cloned())
|
||||
.await
|
||||
.stringify_err()?;
|
||||
// Runtime refresh and tray rebuilding happen after saved node selections are restored.
|
||||
profiles::activate_selected_nodes().stringify_err()?;
|
||||
|
||||
@@ -398,3 +415,44 @@ pub async fn get_next_update_time(uid: String) -> CmdResult<Option<i64>> {
|
||||
let next_time = timer.get_next_update_time(&uid).await;
|
||||
Ok(next_time)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::commit_current_profile;
|
||||
use crate::config::{IProfiles, PrfItem};
|
||||
use clash_verge_draft::Draft;
|
||||
|
||||
fn profile(uid: &str) -> PrfItem {
|
||||
PrfItem {
|
||||
uid: Some(uid.into()),
|
||||
..PrfItem::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn committing_profile_switch_preserves_profiles_added_after_draft_creation() -> anyhow::Result<()> {
|
||||
let profiles = Draft::new(IProfiles {
|
||||
current: Some("a".into()),
|
||||
items: Some(vec![profile("a"), profile("b")]),
|
||||
});
|
||||
profiles.edit_draft(|draft| {
|
||||
draft.patch_config(&IProfiles {
|
||||
current: Some("b".into()),
|
||||
items: None,
|
||||
});
|
||||
});
|
||||
profiles
|
||||
.with_data_modify(|mut committed| async move {
|
||||
committed.items.get_or_insert_with(Vec::new).push(profile("new"));
|
||||
Ok((committed, ()))
|
||||
})
|
||||
.await?;
|
||||
|
||||
commit_current_profile(&profiles, Some("b".into())).await?;
|
||||
|
||||
let committed = profiles.data_arc();
|
||||
assert_eq!(committed.current.as_deref(), Some("b"));
|
||||
assert!(committed.get_item("new").is_ok());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ impl PrfItem {
|
||||
let with_proxy = option.is_some_and(|o| o.with_proxy.unwrap_or(false));
|
||||
let self_proxy = option.is_some_and(|o| o.self_proxy.unwrap_or(false));
|
||||
let accept_invalid_certs = option.is_some_and(|o| o.danger_accept_invalid_certs.unwrap_or(false));
|
||||
let allow_auto_update = option.map(|o| o.allow_auto_update.unwrap_or(true));
|
||||
let allow_auto_update = Some(allow_auto_update_enabled(option));
|
||||
let user_agent = option.and_then(|o| o.user_agent.clone());
|
||||
let update_interval = option.and_then(|o| o.update_interval);
|
||||
let timeout = option.and_then(|o| o.timeout_seconds).unwrap_or(20);
|
||||
@@ -583,6 +583,10 @@ const fn default_allow_auto_update() -> Option<bool> {
|
||||
Some(true)
|
||||
}
|
||||
|
||||
fn allow_auto_update_enabled(option: Option<&PrfOption>) -> bool {
|
||||
option.and_then(|o| o.allow_auto_update).unwrap_or(true)
|
||||
}
|
||||
|
||||
/// Fix URLs where query parameters are incorrectly appended to the path segment
|
||||
///
|
||||
/// Incorrect Example: https://example.com/path¶m1=value1
|
||||
@@ -611,3 +615,19 @@ fn fix_dirty_url(input: &str) -> Result<Url> {
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{PrfOption, allow_auto_update_enabled};
|
||||
|
||||
#[test]
|
||||
fn auto_update_defaults_to_enabled_and_preserves_explicit_false() {
|
||||
assert!(allow_auto_update_enabled(None));
|
||||
|
||||
let disabled = PrfOption {
|
||||
allow_auto_update: Some(false),
|
||||
..PrfOption::default()
|
||||
};
|
||||
assert!(!allow_auto_update_enabled(Some(&disabled)));
|
||||
}
|
||||
}
|
||||
|
||||
+64
-14
@@ -1,4 +1,10 @@
|
||||
use crate::{config::Config, feat, process::AsyncHandler, singleton, utils::resolve::is_resolve_done};
|
||||
use crate::{
|
||||
config::{Config, PrfItem},
|
||||
feat,
|
||||
process::AsyncHandler,
|
||||
singleton,
|
||||
utils::resolve::is_resolve_done,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use clash_verge_logging::{Type, logging, logging_error};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
@@ -43,6 +49,7 @@ impl TaskState {
|
||||
pub struct Timer {
|
||||
command_tx: mpsc::UnboundedSender<TimerCommand>,
|
||||
command_rx: Mutex<Option<mpsc::UnboundedReceiver<TimerCommand>>>,
|
||||
refresh_lock: tokio::sync::Mutex<()>,
|
||||
pub timer_map: Arc<RwLock<HashMap<String, u64>>>,
|
||||
pub initialized: AtomicBool,
|
||||
}
|
||||
@@ -55,6 +62,7 @@ impl Timer {
|
||||
Self {
|
||||
command_tx,
|
||||
command_rx: Mutex::new(Some(command_rx)),
|
||||
refresh_lock: tokio::sync::Mutex::new(()),
|
||||
timer_map: Arc::new(RwLock::new(HashMap::new())),
|
||||
initialized: AtomicBool::new(false),
|
||||
}
|
||||
@@ -99,11 +107,10 @@ impl Timer {
|
||||
}
|
||||
|
||||
let cur_timestamp = chrono::Local::now().timestamp();
|
||||
if let Some(items) = Config::profiles().await.latest_arc().get_items() {
|
||||
if let Some(items) = Config::profiles().await.data_arc().get_items() {
|
||||
for item in items.iter() {
|
||||
if let Some(option) = item.option.as_ref()
|
||||
&& let Some(allow_auto_update) = option.allow_auto_update
|
||||
&& allow_auto_update
|
||||
&& option.allow_auto_update.unwrap_or(true)
|
||||
&& let Some(interval) = option.update_interval
|
||||
&& interval > 0
|
||||
&& let Some(uid) = item.uid.as_ref()
|
||||
@@ -121,6 +128,7 @@ impl Timer {
|
||||
}
|
||||
|
||||
pub async fn refresh(&self) -> Result<()> {
|
||||
let _refresh_guard = self.refresh_lock.lock().await;
|
||||
let new_map = self.gen_map().await;
|
||||
|
||||
let mut cache = self.timer_map.write();
|
||||
@@ -144,20 +152,26 @@ impl Timer {
|
||||
}
|
||||
|
||||
async fn gen_map(&self) -> HashMap<String, u64> {
|
||||
if let Some(items) = Config::profiles().await.data_arc().get_items() {
|
||||
return Self::gen_map_from_items(items);
|
||||
}
|
||||
|
||||
HashMap::new()
|
||||
}
|
||||
|
||||
fn gen_map_from_items(items: &[PrfItem]) -> HashMap<String, u64> {
|
||||
let mut new_map = HashMap::new();
|
||||
|
||||
if let Some(items) = Config::profiles().await.latest_arc().get_items() {
|
||||
for item in items.iter() {
|
||||
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
|
||||
&& interval > 0
|
||||
{
|
||||
new_map.insert(uid.clone(), interval);
|
||||
}
|
||||
for item in items {
|
||||
if let Some(option) = item.option.as_ref()
|
||||
&& let (Some(interval), Some(uid)) = (option.update_interval, &item.uid)
|
||||
&& option.allow_auto_update.unwrap_or(true)
|
||||
&& interval > 0
|
||||
{
|
||||
new_map.insert(uid.clone(), interval);
|
||||
}
|
||||
}
|
||||
|
||||
new_map
|
||||
}
|
||||
|
||||
@@ -414,3 +428,39 @@ impl Timer {
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Timer;
|
||||
use crate::config::{PrfItem, PrfOption};
|
||||
|
||||
fn remote_profile(uid: &str, allow_auto_update: Option<bool>, update_interval: Option<u64>) -> PrfItem {
|
||||
PrfItem {
|
||||
uid: Some(uid.into()),
|
||||
itype: Some("remote".into()),
|
||||
option: Some(PrfOption {
|
||||
allow_auto_update,
|
||||
update_interval,
|
||||
..PrfOption::default()
|
||||
}),
|
||||
..PrfItem::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timer_map_only_contains_enabled_profiles_with_positive_intervals() {
|
||||
let items = vec![
|
||||
remote_profile("enabled", Some(true), Some(30)),
|
||||
remote_profile("disabled", Some(false), Some(30)),
|
||||
remote_profile("missing-flag", None, Some(30)),
|
||||
remote_profile("zero-interval", Some(true), Some(0)),
|
||||
remote_profile("missing-interval", Some(true), None),
|
||||
];
|
||||
|
||||
let map = Timer::gen_map_from_items(&items);
|
||||
|
||||
assert_eq!(map.len(), 2);
|
||||
assert_eq!(map.get("enabled"), Some(&30));
|
||||
assert_eq!(map.get("missing-flag"), Some(&30));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use tauri::Url;
|
||||
|
||||
use crate::{
|
||||
config::{Config, PrfItem, profiles},
|
||||
core::{CoreManager, handle},
|
||||
core::{CoreManager, handle, timer::Timer},
|
||||
utils::help,
|
||||
};
|
||||
use clash_verge_logging::{Type, logging, logging_error};
|
||||
@@ -97,8 +97,12 @@ async fn import_subscription(url: &str, name: Option<&String>) {
|
||||
return;
|
||||
}
|
||||
|
||||
Config::profiles().await.apply();
|
||||
logging_error!(Type::Config, Config::profiles().await.data_arc().save_file().await);
|
||||
if let Err(e) = Config::profiles().await.data_arc().save_file().await {
|
||||
logging!(error, Type::Config, "failed to save imported subscription: {}", e);
|
||||
handle::Handle::notice_message("import_sub_url::error", e.to_string());
|
||||
return;
|
||||
}
|
||||
logging_error!(Type::Timer, Timer::global().refresh().await);
|
||||
handle::Handle::notice_message(
|
||||
"import_sub_url::ok",
|
||||
"", // 空 msg 传入,我们不希望导致 后端-前端-后端 死循环,这里只做提醒。
|
||||
|
||||
Reference in New Issue
Block a user