feat: toggle next auto-update time on subscription card click and show update result feedback

This commit is contained in:
wonfen
2025-04-25 17:17:34 +08:00
parent bd3231bfa8
commit d6a79316a6
10 changed files with 391 additions and 43 deletions

View File

@@ -1,11 +1,12 @@
use super::CmdResult;
use crate::{
config::{Config, IProfiles, PrfItem, PrfOption},
core::{handle, tray::Tray, CoreManager},
core::{handle, timer::Timer, tray::Tray, CoreManager},
feat, logging, ret_err,
utils::{dirs, help, logging::Type},
wrap_err,
};
use tauri::Emitter;
/// 获取配置文件列表
#[tauri::command]
@@ -45,7 +46,7 @@ pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResu
/// 更新配置文件
#[tauri::command]
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
wrap_err!(feat::update_profile(index, option).await)
wrap_err!(feat::update_profile(index, option, Some(true)).await)
}
/// 删除配置文件
@@ -211,7 +212,35 @@ pub async fn patch_profiles_config_by_profile_index(
/// 修改某个profile item的
#[tauri::command]
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
wrap_err!(Config::profiles().data().patch_item(index, profile))?;
// 保存修改前检查是否有更新 update_interval
let update_interval_changed =
if let Ok(old_profile) = Config::profiles().latest().get_item(&index) {
let old_interval = old_profile.option.as_ref().and_then(|o| o.update_interval);
let new_interval = profile.option.as_ref().and_then(|o| o.update_interval);
old_interval != new_interval
} else {
false
};
// 保存修改
wrap_err!(Config::profiles().data().patch_item(index.clone(), profile))?;
// 如果更新间隔变更,异步刷新定时器
if update_interval_changed {
let index_clone = index.clone();
crate::process::AsyncHandler::spawn(move || async move {
logging!(info, Type::Timer, "定时器更新间隔已变更,正在刷新定时器...");
if let Err(e) = crate::core::Timer::global().refresh() {
logging!(error, Type::Timer, "刷新定时器失败: {}", e);
} else {
// 刷新成功后发送自定义事件,不触发配置重载
if let Some(window) = crate::core::handle::Handle::global().get_window() {
let _ = window.emit("verge://timer-updated", index_clone);
}
}
});
}
Ok(())
}
@@ -242,3 +271,11 @@ pub fn read_profile_file(index: String) -> CmdResult<String> {
let data = wrap_err!(item.read_file())?;
Ok(data)
}
/// 获取下一次更新时间
#[tauri::command]
pub fn get_next_update_time(uid: String) -> CmdResult<Option<i64>> {
let timer = Timer::global();
let next_time = timer.get_next_update_time(&uid);
Ok(next_time)
}

View File

@@ -10,3 +10,4 @@ pub mod tray;
pub mod win_uwp;
pub use self::core::*;
pub use self::timer::Timer;

View File

@@ -1,6 +1,4 @@
use crate::{
config::Config, core::CoreManager, feat, logging, logging_error, utils::logging::Type,
};
use crate::{config::Config, feat, logging, logging_error, utils::logging::Type};
use anyhow::{Context, Result};
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
use once_cell::sync::OnceCell;
@@ -71,6 +69,25 @@ impl Timer {
return Err(e);
}
let timer_map = self.timer_map.read();
logging!(
info,
Type::Timer,
"已注册的定时任务数量: {}",
timer_map.len()
);
for (uid, task) in timer_map.iter() {
logging!(
info,
Type::Timer,
"注册了定时任务 - uid={}, interval={}min, task_id={}",
uid,
task.interval_minutes,
task.task_id
);
}
let cur_timestamp = chrono::Local::now().timestamp();
// Collect profiles that need immediate update
@@ -83,6 +100,7 @@ impl Timer {
let uid = item.uid.as_ref()?;
if interval > 0 && cur_timestamp - updated >= interval * 60 {
logging!(info, Type::Timer, "需要立即更新的配置: uid={}", uid);
Some(uid.clone())
} else {
None
@@ -95,12 +113,18 @@ impl Timer {
// Advance tasks outside of locks to minimize lock contention
if !profiles_to_update.is_empty() {
logging!(
info,
Type::Timer,
"需要立即更新的配置数量: {}",
profiles_to_update.len()
);
let timer_map = self.timer_map.read();
let delay_timer = self.delay_timer.write();
for uid in profiles_to_update {
if let Some(task) = timer_map.get(&uid) {
logging!(info, Type::Timer, "Advancing task for uid: {}", uid);
logging!(info, Type::Timer, "立即执行任务: uid={}", uid);
if let Err(e) = delay_timer.advance_task(task.task_id) {
logging!(warn, Type::Timer, "Failed to advance task {}: {}", uid, e);
}
@@ -210,6 +234,13 @@ impl Timer {
if let Some(option) = item.option.as_ref() {
if let (Some(interval), Some(uid)) = (option.update_interval, &item.uid) {
if interval > 0 {
logging!(
debug,
Type::Timer,
"找到定时更新配置: uid={}, interval={}min",
uid,
interval
);
new_map.insert(uid.clone(), interval);
}
}
@@ -217,6 +248,12 @@ impl Timer {
}
}
logging!(
debug,
Type::Timer,
"生成的定时更新配置数量: {}",
new_map.len()
);
new_map
}
@@ -227,20 +264,36 @@ impl Timer {
// Read lock for comparing current state
let timer_map = self.timer_map.read();
logging!(
debug,
Type::Timer,
"当前 timer_map 大小: {}",
timer_map.len()
);
// Find tasks to modify or delete
for (uid, task) in timer_map.iter() {
match new_map.get(uid) {
Some(&interval) if interval != task.interval_minutes => {
// Task exists but interval changed
logging!(
debug,
Type::Timer,
"定时任务间隔变更: uid={}, 旧={}, 新={}",
uid,
task.interval_minutes,
interval
);
diff_map.insert(uid.clone(), DiffFlag::Mod(task.task_id, interval));
}
None => {
// Task no longer needed
logging!(debug, Type::Timer, "定时任务已删除: uid={}", uid);
diff_map.insert(uid.clone(), DiffFlag::Del(task.task_id));
}
_ => {
// Task exists with same interval, no change needed
logging!(debug, Type::Timer, "定时任务保持不变: uid={}", uid);
}
}
}
@@ -250,6 +303,13 @@ impl Timer {
for (uid, &interval) in new_map.iter() {
if !timer_map.contains_key(uid) {
logging!(
debug,
Type::Timer,
"新增定时任务: uid={}, interval={}min",
uid,
interval
);
diff_map.insert(uid.clone(), DiffFlag::Add(next_id, interval));
next_id += 1;
}
@@ -260,6 +320,7 @@ impl Timer {
*self.timer_count.lock() = next_id;
}
logging!(debug, Type::Timer, "定时任务变更数量: {}", diff_map.len());
diff_map
}
@@ -300,42 +361,126 @@ impl Timer {
Ok(())
}
/// Get next update time for a profile
pub fn get_next_update_time(&self, uid: &str) -> Option<i64> {
logging!(info, Type::Timer, "获取下次更新时间uid={}", uid);
let timer_map = self.timer_map.read();
let task = match timer_map.get(uid) {
Some(t) => t,
None => {
logging!(warn, Type::Timer, "找不到对应的定时任务uid={}", uid);
return None;
}
};
// Get the profile updated timestamp
let profiles_config = Config::profiles();
let profiles = profiles_config.latest();
let items = match profiles.get_items() {
Some(i) => i,
None => {
logging!(warn, Type::Timer, "获取配置列表失败");
return None;
}
};
// 修复类型比较,使用字符串值比较而不是引用比较
let profile = match items
.iter()
.find(|item| item.uid.as_ref().map(|u| u.as_str()) == Some(uid))
{
Some(p) => p,
None => {
logging!(warn, Type::Timer, "找不到对应的配置uid={}", uid);
return None;
}
};
let updated = profile.updated.unwrap_or(0) as i64;
// Calculate next update time
if updated > 0 && task.interval_minutes > 0 {
let next_time = updated + (task.interval_minutes as i64 * 60);
logging!(
info,
Type::Timer,
"计算得到下次更新时间: {}, uid={}",
next_time,
uid
);
Some(next_time)
} else {
logging!(
warn,
Type::Timer,
"更新时间或间隔无效updated={}, interval={}",
updated,
task.interval_minutes
);
None
}
}
/// Emit update events for frontend notification
fn emit_update_event(_uid: &str, _is_start: bool) {
// 当feature="verge-dev"或"default"时才启用此功能
#[cfg(any(feature = "verge-dev", feature = "default"))]
{
use serde_json::json;
use tauri::Emitter;
let event_name = if _is_start {
"profile-update-started"
} else {
"profile-update-completed"
};
if let Some(window) = super::handle::Handle::global().get_window() {
let _ = window.emit(event_name, json!({ "uid": _uid }));
}
}
}
/// Async task with better error handling and logging
async fn async_task(uid: String) {
let task_start = std::time::Instant::now();
logging!(info, Type::Timer, "Running timer task for profile: {}", uid);
// Update profile
let profile_result = feat::update_profile(uid.clone(), None).await;
// Emit start event
Self::emit_update_event(&uid, true);
// 检查是否是当前激活的配置文件
let is_current = Config::profiles().latest().current.as_ref() == Some(&uid);
logging!(
info,
Type::Timer,
"配置 {} 是否为当前激活配置: {}",
uid,
is_current
);
// Update profile - 由update_profile函数自动处理是否需要刷新UI
let profile_result = feat::update_profile(uid.clone(), None, Some(is_current)).await;
match profile_result {
Ok(_) => {
// Update configuration
match CoreManager::global().update_config().await {
Ok(_) => {
let duration = task_start.elapsed().as_millis();
logging!(
info,
Type::Timer,
"Timer task completed successfully for uid: {} (took {}ms)",
uid,
duration
);
}
Err(e) => {
logging_error!(
Type::Timer,
"Failed to refresh config after profile update for uid {}: {}",
uid,
e
);
}
}
let duration = task_start.elapsed().as_millis();
logging!(
info,
Type::Timer,
"Timer task completed successfully for uid: {} (took {}ms)",
uid,
duration
);
}
Err(e) => {
logging_error!(Type::Timer, "Failed to update profile uid {}: {}", uid, e);
}
}
// Emit completed event
Self::emit_update_event(&uid, false);
}
}

View File

@@ -23,8 +23,14 @@ pub fn toggle_proxy_profile(profile_index: String) {
/// Update a profile
/// If updating current profile, activate it
pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()> {
/// auto_refresh: 是否自动更新配置和刷新前端
pub async fn update_profile(
uid: String,
option: Option<PrfOption>,
auto_refresh: Option<bool>,
) -> Result<()> {
println!("[订阅更新] 开始更新订阅 {}", uid);
let auto_refresh = auto_refresh.unwrap_or(true); // 默认为true保持兼容性
let url_opt = {
let profiles = Config::profiles();
@@ -63,7 +69,7 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
let is_current = Some(uid.clone()) == profiles.get_current();
println!("[订阅更新] 是否为当前使用的订阅: {}", is_current);
is_current
is_current && auto_refresh
}
Err(err) => {
// 首次更新失败尝试使用Clash代理
@@ -105,7 +111,7 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
let is_current = Some(uid.clone()) == profiles.get_current();
println!("[订阅更新] 是否为当前使用的订阅: {}", is_current);
is_current
is_current && auto_refresh
}
Err(retry_err) => {
println!("[订阅更新] 使用Clash代理更新仍然失败: {}", retry_err);
@@ -119,7 +125,7 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
}
}
}
None => true,
None => auto_refresh,
};
if should_update {

View File

@@ -213,6 +213,7 @@ pub fn run() {
cmd::delete_profile,
cmd::read_profile_file,
cmd::save_profile_file,
cmd::get_next_update_time,
// script validation
cmd::script_validate_notice,
cmd::validate_script_file,