mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 04:53:44 +08:00
feat: toggle next auto-update time on subscription card click and show update result feedback
This commit is contained in:
parent
bd3231bfa8
commit
d6a79316a6
@ -28,6 +28,7 @@
|
|||||||
- 系统代理守卫可检查意外设置变更并恢复
|
- 系统代理守卫可检查意外设置变更并恢复
|
||||||
- 定时自动订阅更新也能自动回退使用代理
|
- 定时自动订阅更新也能自动回退使用代理
|
||||||
- 订阅请求超时机制,防止订阅更新的时候卡死
|
- 订阅请求超时机制,防止订阅更新的时候卡死
|
||||||
|
- 订阅卡片点击时间可切换下次自动更新时间,自动更新触发后页面有明确的成功与否提示
|
||||||
|
|
||||||
#### 优化了:
|
#### 优化了:
|
||||||
- 系统代理 Bypass 设置
|
- 系统代理 Bypass 设置
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
use super::CmdResult;
|
use super::CmdResult;
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Config, IProfiles, PrfItem, PrfOption},
|
config::{Config, IProfiles, PrfItem, PrfOption},
|
||||||
core::{handle, tray::Tray, CoreManager},
|
core::{handle, timer::Timer, tray::Tray, CoreManager},
|
||||||
feat, logging, ret_err,
|
feat, logging, ret_err,
|
||||||
utils::{dirs, help, logging::Type},
|
utils::{dirs, help, logging::Type},
|
||||||
wrap_err,
|
wrap_err,
|
||||||
};
|
};
|
||||||
|
use tauri::Emitter;
|
||||||
|
|
||||||
/// 获取配置文件列表
|
/// 获取配置文件列表
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -45,7 +46,7 @@ pub async fn create_profile(item: PrfItem, file_data: Option<String>) -> CmdResu
|
|||||||
/// 更新配置文件
|
/// 更新配置文件
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn update_profile(index: String, option: Option<PrfOption>) -> CmdResult {
|
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的
|
/// 修改某个profile item的
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,3 +271,11 @@ pub fn read_profile_file(index: String) -> CmdResult<String> {
|
|||||||
let data = wrap_err!(item.read_file())?;
|
let data = wrap_err!(item.read_file())?;
|
||||||
Ok(data)
|
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)
|
||||||
|
}
|
||||||
|
@ -10,3 +10,4 @@ pub mod tray;
|
|||||||
pub mod win_uwp;
|
pub mod win_uwp;
|
||||||
|
|
||||||
pub use self::core::*;
|
pub use self::core::*;
|
||||||
|
pub use self::timer::Timer;
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
use crate::{
|
use crate::{config::Config, feat, logging, logging_error, utils::logging::Type};
|
||||||
config::Config, core::CoreManager, feat, logging, logging_error, utils::logging::Type,
|
|
||||||
};
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
|
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
@ -71,6 +69,25 @@ impl Timer {
|
|||||||
return Err(e);
|
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();
|
let cur_timestamp = chrono::Local::now().timestamp();
|
||||||
|
|
||||||
// Collect profiles that need immediate update
|
// Collect profiles that need immediate update
|
||||||
@ -83,6 +100,7 @@ impl Timer {
|
|||||||
let uid = item.uid.as_ref()?;
|
let uid = item.uid.as_ref()?;
|
||||||
|
|
||||||
if interval > 0 && cur_timestamp - updated >= interval * 60 {
|
if interval > 0 && cur_timestamp - updated >= interval * 60 {
|
||||||
|
logging!(info, Type::Timer, "需要立即更新的配置: uid={}", uid);
|
||||||
Some(uid.clone())
|
Some(uid.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -95,12 +113,18 @@ impl Timer {
|
|||||||
|
|
||||||
// Advance tasks outside of locks to minimize lock contention
|
// Advance tasks outside of locks to minimize lock contention
|
||||||
if !profiles_to_update.is_empty() {
|
if !profiles_to_update.is_empty() {
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Timer,
|
||||||
|
"需要立即更新的配置数量: {}",
|
||||||
|
profiles_to_update.len()
|
||||||
|
);
|
||||||
let timer_map = self.timer_map.read();
|
let timer_map = self.timer_map.read();
|
||||||
let delay_timer = self.delay_timer.write();
|
let delay_timer = self.delay_timer.write();
|
||||||
|
|
||||||
for uid in profiles_to_update {
|
for uid in profiles_to_update {
|
||||||
if let Some(task) = timer_map.get(&uid) {
|
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) {
|
if let Err(e) = delay_timer.advance_task(task.task_id) {
|
||||||
logging!(warn, Type::Timer, "Failed to advance task {}: {}", uid, e);
|
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(option) = item.option.as_ref() {
|
||||||
if let (Some(interval), Some(uid)) = (option.update_interval, &item.uid) {
|
if let (Some(interval), Some(uid)) = (option.update_interval, &item.uid) {
|
||||||
if interval > 0 {
|
if interval > 0 {
|
||||||
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Timer,
|
||||||
|
"找到定时更新配置: uid={}, interval={}min",
|
||||||
|
uid,
|
||||||
|
interval
|
||||||
|
);
|
||||||
new_map.insert(uid.clone(), interval);
|
new_map.insert(uid.clone(), interval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,6 +248,12 @@ impl Timer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Timer,
|
||||||
|
"生成的定时更新配置数量: {}",
|
||||||
|
new_map.len()
|
||||||
|
);
|
||||||
new_map
|
new_map
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,20 +264,36 @@ impl Timer {
|
|||||||
|
|
||||||
// Read lock for comparing current state
|
// Read lock for comparing current state
|
||||||
let timer_map = self.timer_map.read();
|
let timer_map = self.timer_map.read();
|
||||||
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Timer,
|
||||||
|
"当前 timer_map 大小: {}",
|
||||||
|
timer_map.len()
|
||||||
|
);
|
||||||
|
|
||||||
// Find tasks to modify or delete
|
// Find tasks to modify or delete
|
||||||
for (uid, task) in timer_map.iter() {
|
for (uid, task) in timer_map.iter() {
|
||||||
match new_map.get(uid) {
|
match new_map.get(uid) {
|
||||||
Some(&interval) if interval != task.interval_minutes => {
|
Some(&interval) if interval != task.interval_minutes => {
|
||||||
// Task exists but interval changed
|
// 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));
|
diff_map.insert(uid.clone(), DiffFlag::Mod(task.task_id, interval));
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Task no longer needed
|
// Task no longer needed
|
||||||
|
logging!(debug, Type::Timer, "定时任务已删除: uid={}", uid);
|
||||||
diff_map.insert(uid.clone(), DiffFlag::Del(task.task_id));
|
diff_map.insert(uid.clone(), DiffFlag::Del(task.task_id));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Task exists with same interval, no change needed
|
// 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() {
|
for (uid, &interval) in new_map.iter() {
|
||||||
if !timer_map.contains_key(uid) {
|
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));
|
diff_map.insert(uid.clone(), DiffFlag::Add(next_id, interval));
|
||||||
next_id += 1;
|
next_id += 1;
|
||||||
}
|
}
|
||||||
@ -260,6 +320,7 @@ impl Timer {
|
|||||||
*self.timer_count.lock() = next_id;
|
*self.timer_count.lock() = next_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logging!(debug, Type::Timer, "定时任务变更数量: {}", diff_map.len());
|
||||||
diff_map
|
diff_map
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,42 +361,126 @@ impl Timer {
|
|||||||
Ok(())
|
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 task with better error handling and logging
|
||||||
async fn async_task(uid: String) {
|
async fn async_task(uid: String) {
|
||||||
let task_start = std::time::Instant::now();
|
let task_start = std::time::Instant::now();
|
||||||
logging!(info, Type::Timer, "Running timer task for profile: {}", uid);
|
logging!(info, Type::Timer, "Running timer task for profile: {}", uid);
|
||||||
|
|
||||||
// Update profile
|
// Emit start event
|
||||||
let profile_result = feat::update_profile(uid.clone(), None).await;
|
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 {
|
match profile_result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// Update configuration
|
let duration = task_start.elapsed().as_millis();
|
||||||
match CoreManager::global().update_config().await {
|
logging!(
|
||||||
Ok(_) => {
|
info,
|
||||||
let duration = task_start.elapsed().as_millis();
|
Type::Timer,
|
||||||
logging!(
|
"Timer task completed successfully for uid: {} (took {}ms)",
|
||||||
info,
|
uid,
|
||||||
Type::Timer,
|
duration
|
||||||
"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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging_error!(Type::Timer, "Failed to update profile uid {}: {}", uid, e);
|
logging_error!(Type::Timer, "Failed to update profile uid {}: {}", uid, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Emit completed event
|
||||||
|
Self::emit_update_event(&uid, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +23,14 @@ pub fn toggle_proxy_profile(profile_index: String) {
|
|||||||
|
|
||||||
/// Update a profile
|
/// Update a profile
|
||||||
/// If updating current profile, activate it
|
/// 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);
|
println!("[订阅更新] 开始更新订阅 {}", uid);
|
||||||
|
let auto_refresh = auto_refresh.unwrap_or(true); // 默认为true,保持兼容性
|
||||||
|
|
||||||
let url_opt = {
|
let url_opt = {
|
||||||
let profiles = Config::profiles();
|
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();
|
let is_current = Some(uid.clone()) == profiles.get_current();
|
||||||
println!("[订阅更新] 是否为当前使用的订阅: {}", is_current);
|
println!("[订阅更新] 是否为当前使用的订阅: {}", is_current);
|
||||||
is_current
|
is_current && auto_refresh
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// 首次更新失败,尝试使用Clash代理
|
// 首次更新失败,尝试使用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();
|
let is_current = Some(uid.clone()) == profiles.get_current();
|
||||||
println!("[订阅更新] 是否为当前使用的订阅: {}", is_current);
|
println!("[订阅更新] 是否为当前使用的订阅: {}", is_current);
|
||||||
is_current
|
is_current && auto_refresh
|
||||||
}
|
}
|
||||||
Err(retry_err) => {
|
Err(retry_err) => {
|
||||||
println!("[订阅更新] 使用Clash代理更新仍然失败: {}", 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 {
|
if should_update {
|
||||||
|
@ -213,6 +213,7 @@ pub fn run() {
|
|||||||
cmd::delete_profile,
|
cmd::delete_profile,
|
||||||
cmd::read_profile_file,
|
cmd::read_profile_file,
|
||||||
cmd::save_profile_file,
|
cmd::save_profile_file,
|
||||||
|
cmd::get_next_update_time,
|
||||||
// script validation
|
// script validation
|
||||||
cmd::script_validate_notice,
|
cmd::script_validate_notice,
|
||||||
cmd::validate_script_file,
|
cmd::validate_script_file,
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
readProfileFile,
|
readProfileFile,
|
||||||
updateProfile,
|
updateProfile,
|
||||||
saveProfileFile,
|
saveProfileFile,
|
||||||
|
getNextUpdateTime,
|
||||||
} from "@/services/cmds";
|
} from "@/services/cmds";
|
||||||
import { Notice } from "@/components/base";
|
import { Notice } from "@/components/base";
|
||||||
import { GroupsEditorViewer } from "@/components/profile/groups-editor-viewer";
|
import { GroupsEditorViewer } from "@/components/profile/groups-editor-viewer";
|
||||||
@ -65,9 +66,107 @@ export const ProfileItem = (props: Props) => {
|
|||||||
const [position, setPosition] = useState({ left: 0, top: 0 });
|
const [position, setPosition] = useState({ left: 0, top: 0 });
|
||||||
const loadingCache = useLoadingCache();
|
const loadingCache = useLoadingCache();
|
||||||
const setLoadingCache = useSetLoadingCache();
|
const setLoadingCache = useSetLoadingCache();
|
||||||
|
|
||||||
|
// 新增状态:是否显示下次更新时间
|
||||||
|
const [showNextUpdate, setShowNextUpdate] = useState(false);
|
||||||
|
const [nextUpdateTime, setNextUpdateTime] = useState("");
|
||||||
|
|
||||||
const { uid, name = "Profile", extra, updated = 0, option } = itemData;
|
const { uid, name = "Profile", extra, updated = 0, option } = itemData;
|
||||||
|
|
||||||
|
// 获取下次更新时间的函数
|
||||||
|
const fetchNextUpdateTime = useLockFn(async (forceRefresh = false) => {
|
||||||
|
if (itemData.option?.update_interval && itemData.option.update_interval > 0) {
|
||||||
|
try {
|
||||||
|
console.log(`尝试获取配置 ${itemData.uid} 的下次更新时间`);
|
||||||
|
|
||||||
|
// 如果需要强制刷新,先触发Timer.refresh()
|
||||||
|
if (forceRefresh) {
|
||||||
|
// 这里可以通过一个新的API来触发刷新,但目前我们依赖patch_profile中的刷新
|
||||||
|
console.log(`强制刷新定时器任务`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextUpdate = await getNextUpdateTime(itemData.uid);
|
||||||
|
console.log(`获取到下次更新时间结果:`, nextUpdate);
|
||||||
|
|
||||||
|
if (nextUpdate) {
|
||||||
|
const nextUpdateDate = dayjs(nextUpdate * 1000);
|
||||||
|
const now = dayjs();
|
||||||
|
|
||||||
|
// 如果已经过期,显示"更新失败"
|
||||||
|
if (nextUpdateDate.isBefore(now)) {
|
||||||
|
setNextUpdateTime(t("Last Update failed"));
|
||||||
|
} else {
|
||||||
|
// 否则显示剩余时间
|
||||||
|
const diffMinutes = nextUpdateDate.diff(now, 'minute');
|
||||||
|
|
||||||
|
if (diffMinutes < 60) {
|
||||||
|
if (diffMinutes <= 0) {
|
||||||
|
setNextUpdateTime(`${t("Next Up")} <1m`);
|
||||||
|
} else {
|
||||||
|
setNextUpdateTime(`${t("Next Up")} ${diffMinutes}m`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const hours = Math.floor(diffMinutes / 60);
|
||||||
|
const mins = diffMinutes % 60;
|
||||||
|
setNextUpdateTime(`${t("Next Up")} ${hours}h ${mins}m`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`返回的下次更新时间为空`);
|
||||||
|
setNextUpdateTime(t("No schedule"));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`获取下次更新时间出错:`, err);
|
||||||
|
setNextUpdateTime(t("Unknown"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`该配置未设置更新间隔或间隔为0`);
|
||||||
|
setNextUpdateTime(t("Auto update disabled"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 切换显示模式的函数
|
||||||
|
const toggleUpdateTimeDisplay = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (!showNextUpdate) {
|
||||||
|
fetchNextUpdateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowNextUpdate(!showNextUpdate);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 当组件加载或更新间隔变化时更新下次更新时间
|
||||||
|
useEffect(() => {
|
||||||
|
if (showNextUpdate) {
|
||||||
|
fetchNextUpdateTime();
|
||||||
|
}
|
||||||
|
}, [showNextUpdate, itemData.option?.update_interval, updated]);
|
||||||
|
|
||||||
|
// 订阅定时器更新事件
|
||||||
|
useEffect(() => {
|
||||||
|
// 处理定时器更新事件 - 这个事件专门用于通知定时器变更
|
||||||
|
const handleTimerUpdate = (event: any) => {
|
||||||
|
const updatedUid = event.payload as string;
|
||||||
|
|
||||||
|
// 只有当更新的是当前配置时才刷新显示
|
||||||
|
if (updatedUid === itemData.uid && showNextUpdate) {
|
||||||
|
console.log(`收到定时器更新事件: uid=${updatedUid}`);
|
||||||
|
setTimeout(() => {
|
||||||
|
fetchNextUpdateTime(true);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 只注册定时器更新事件监听
|
||||||
|
window.addEventListener('verge://timer-updated', handleTimerUpdate as EventListener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// 清理事件监听
|
||||||
|
window.removeEventListener('verge://timer-updated', handleTimerUpdate as EventListener);
|
||||||
|
};
|
||||||
|
}, [showNextUpdate, itemData.uid]);
|
||||||
|
|
||||||
// local file mode
|
// local file mode
|
||||||
// remote file mode
|
// remote file mode
|
||||||
// remote file mode
|
// remote file mode
|
||||||
@ -303,6 +402,35 @@ export const ProfileItem = (props: Props) => {
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听自动更新事件
|
||||||
|
useEffect(() => {
|
||||||
|
const handleUpdateStarted = (event: CustomEvent) => {
|
||||||
|
if (event.detail.uid === itemData.uid) {
|
||||||
|
setLoadingCache((cache) => ({ ...cache, [itemData.uid]: true }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateCompleted = (event: CustomEvent) => {
|
||||||
|
if (event.detail.uid === itemData.uid) {
|
||||||
|
setLoadingCache((cache) => ({ ...cache, [itemData.uid]: false }));
|
||||||
|
// 更新完成后刷新显示
|
||||||
|
if (showNextUpdate) {
|
||||||
|
fetchNextUpdateTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 注册事件监听
|
||||||
|
window.addEventListener('profile-update-started', handleUpdateStarted as EventListener);
|
||||||
|
window.addEventListener('profile-update-completed', handleUpdateCompleted as EventListener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// 清理事件监听
|
||||||
|
window.removeEventListener('profile-update-started', handleUpdateStarted as EventListener);
|
||||||
|
window.removeEventListener('profile-update-completed', handleUpdateCompleted as EventListener);
|
||||||
|
};
|
||||||
|
}, [itemData.uid, showNextUpdate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -413,15 +541,30 @@ export const ProfileItem = (props: Props) => {
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
{hasUrl && (
|
{hasUrl && (
|
||||||
<Typography
|
<Box sx={{ display: "flex", justifyContent: "flex-end", ml: "auto" }}>
|
||||||
noWrap
|
<Typography
|
||||||
flex="1 0 auto"
|
noWrap
|
||||||
fontSize={14}
|
component="span"
|
||||||
textAlign="right"
|
fontSize={14}
|
||||||
title={`${t("Update Time")}: ${parseExpire(updated)}`}
|
textAlign="right"
|
||||||
>
|
title={showNextUpdate ? t("Click to show last update time") : `${t("Update Time")}: ${parseExpire(updated)}\n${t("Click to show next update")}`}
|
||||||
{updated > 0 ? dayjs(updated * 1000).fromNow() : ""}
|
sx={{
|
||||||
</Typography>
|
cursor: "pointer",
|
||||||
|
display: "inline-block",
|
||||||
|
borderBottom: "1px dashed transparent",
|
||||||
|
transition: "all 0.2s",
|
||||||
|
"&:hover": {
|
||||||
|
borderBottomColor: "primary.main",
|
||||||
|
color: "primary.main",
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={toggleUpdateTimeDisplay}
|
||||||
|
>
|
||||||
|
{showNextUpdate
|
||||||
|
? nextUpdateTime
|
||||||
|
: (updated > 0 ? dayjs(updated * 1000).fromNow() : "")}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -563,6 +563,11 @@
|
|||||||
"Administrator + Service Mode": "Admin + Service Mode",
|
"Administrator + Service Mode": "Admin + Service Mode",
|
||||||
"Last Check Update": "Last Check Update",
|
"Last Check Update": "Last Check Update",
|
||||||
"Click to import subscription": "Click to import subscription",
|
"Click to import subscription": "Click to import subscription",
|
||||||
|
"Last Update failed": "Last Update failed",
|
||||||
|
"Next Up": "Next Up",
|
||||||
|
"No schedule": "No schedule",
|
||||||
|
"Unknown": "Unknown",
|
||||||
|
"Auto update disabled": "Auto update disabled",
|
||||||
"Update subscription successfully": "Update subscription successfully",
|
"Update subscription successfully": "Update subscription successfully",
|
||||||
"Update failed, retrying with Clash proxy...": "Update failed, retrying with Clash proxy...",
|
"Update failed, retrying with Clash proxy...": "Update failed, retrying with Clash proxy...",
|
||||||
"Update with Clash proxy successfully": "Update with Clash proxy successfully",
|
"Update with Clash proxy successfully": "Update with Clash proxy successfully",
|
||||||
|
@ -563,6 +563,11 @@
|
|||||||
"Administrator + Service Mode": "管理员 + 服务模式",
|
"Administrator + Service Mode": "管理员 + 服务模式",
|
||||||
"Last Check Update": "最后检查更新",
|
"Last Check Update": "最后检查更新",
|
||||||
"Click to import subscription": "点击导入订阅",
|
"Click to import subscription": "点击导入订阅",
|
||||||
|
"Last Update failed": "上次更新失败",
|
||||||
|
"Next Up": "下次更新",
|
||||||
|
"No schedule": "没有计划",
|
||||||
|
"Unknown": "未知",
|
||||||
|
"Auto update disabled": "自动更新已禁用",
|
||||||
"Update subscription successfully": "订阅更新成功",
|
"Update subscription successfully": "订阅更新成功",
|
||||||
"Update failed, retrying with Clash proxy...": "订阅更新失败,尝试使用 Clash 代理更新",
|
"Update failed, retrying with Clash proxy...": "订阅更新失败,尝试使用 Clash 代理更新",
|
||||||
"Update with Clash proxy successfully": "使用 Clash 代理更新成功",
|
"Update with Clash proxy successfully": "使用 Clash 代理更新成功",
|
||||||
|
@ -359,3 +359,7 @@ export const isAdmin = async () => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function getNextUpdateTime(uid: string) {
|
||||||
|
return invoke<number | null>("get_next_update_time", { uid });
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user