mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-21 09:45:48 +08:00
* feat: 实现启动时自动更新已订阅脚本及多仓库分离存储
- ScriptConfig: 新增 AutoUpdateSubscribedScripts 配置项
- ScriptRepoUpdater: 动态 CenterRepoPath, 按仓库URL分离存储
- 内容重合度检测(Jaccard系数)判断仓库异同
- URL→文件夹名持久化映射(repo_folder_mapping.json)
- repo_updated.json 存放于各自仓库文件夹内
- AutoUpdateSubscribedScripts 启动时自动更新订阅脚本
- 静默同步仓库、渠道URL解析、检出更新脚本
- RepoWebBridge: 使用动态路径, 辅助方法改为 internal
- MainWindowViewModel: 启动时调用自动更新
* feat: 基于内容重合度的导入zip仓库
* perf: 合并默认仓库url映射
* perf: 清理兼容字段
* perf: 添加线程锁以避免并发调用
* fix: 缓存FolderMapping、修复重合度异常返回值、目录扫描异常隔离、移除未使用变量
* perf: 优化更新流程
* perf: 内存缓存添加锁
* fix: 修复更新状态逻辑,确保克隆失败时不标记为已更新
* perf: 文件夹映射先写磁盘再写缓存
* refactor: 简化生成唯一文件夹名称的方法,移除不必要的参数
* fix: ResetRepo加写锁并清理URL映射条目
* perf: 优化重合度算法
* docs: 更新注释
* fix: 仅重置实际更新成功的脚本的 hasUpdate 标记
* feat: 手动一键更新按钮
* feat: 订阅路径迁移至独立文件存储并简化更新逻辑
- 订阅数据从 config.json 迁移到 User/subscriptions/{repo}.json 独立文件
- 添加 ReaderWriterLockSlim 保护订阅文件并发读写
- 使用 System.Text.Json + ConfigService.JsonOptions 序列化
- 新增 RepoWebBridge.GetSubscribedScriptPaths() 桥接方法
- 启动时自动从旧 config.json 迁移订阅数据到独立文件
- 合并手动/自动更新为 UpdateAllSubscribedScriptsCore 共用核心
- 移除 hasUpdate 检查,直接全量更新所有订阅脚本
- 移除冗余 logPrefix 参数
* refactor: 简化启动时自动更新调用
- 移除 Task.Run + try-catch 包装,异常处理已内置于方法中
- 直接使用 fire-and-forget 异步调用
* fix: 订阅目录命名改为 PascalCase (Subscriptions)
* refactor: 移除死代码和冗余中间层方法
* fix: ReadSubscriptionFile 异常时记录日志避免订阅数据静默丢失
* fix: 进度条改为Indeterminate模式、异常日志补全、订阅去重、迁移批量写入、锁注释
* refactor: 提取 ReadFolderMappingFromDisk 消除映射方法嵌套 try
* fix: 补全静态方法异常日志、WriteSubscriptionFile异常保护、ManualUpdate注释
* fix: ManualUpdateSubscribedScripts 加 try-catch 兜底并提示用户重置仓库
* fix: Dialog打开时检测后台自动更新状态,自动禁用按钮并显示进度提示
- ScriptRepoUpdater 新增 IsAutoUpdating 标志和 AutoUpdateStateChanged 事件
- ScriptRepoWindow 订阅事件,自动更新期间显示进度条、禁用所有操作按钮并 Toast 提示
- 更新仓库/重置仓库按钮也加上 IsEnabled 绑定 IsUpdating
* fix: 将自动更新调用包裹在 Task.Run 中避免 UI 线程阻塞
AutoUpdateSubscribedScripts 的 await 后续会被 WPF SynchronizationContext
调度回 UI 线程,导致大量 Git checkout 和文件 IO 操作阻塞界面。
用 Task.Run 确保整个流程在线程池执行。
* fix: 进度条分离 IsProgressIndeterminate 属性,按操作类型正确切换确定/不确定模式
* refactor: 消除 pathing 展开重复逻辑、用布尔字段替换字符串比较追踪状态来源、补全锁注释、统一日志方式
* fix: ExpandTopLevelPaths 泛化展开所有 PathMapper 顶层 key 防止误删用户目录,迁移移入锁内
* fix: 命令行启动配置组/一条龙前先等待自动更新订阅脚本完成
* feat: 添加命令行启动时是否先自动更新选项
* fix: 修复按钮位置
599 lines
18 KiB
C#
599 lines
18 KiB
C#
using BetterGenshinImpact.Core.Config;
|
||
using BetterGenshinImpact.Core.Script;
|
||
using BetterGenshinImpact.GameTask;
|
||
using BetterGenshinImpact.Helpers;
|
||
using BetterGenshinImpact.Helpers.Ui;
|
||
using BetterGenshinImpact.Helpers.Win32;
|
||
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using Meziantou.Framework.Win32;
|
||
using Microsoft.Win32;
|
||
using System;
|
||
using System.Collections.ObjectModel;
|
||
using System.ComponentModel;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
using System.IO.Compression;
|
||
using System.Linq;
|
||
using System.Net.Http;
|
||
using System.Threading.Tasks;
|
||
using System.Windows;
|
||
using System.Windows.Data;
|
||
using System.Globalization;
|
||
using System.Windows.Navigation;
|
||
using Wpf.Ui.Violeta.Controls;
|
||
|
||
namespace BetterGenshinImpact.View.Windows;
|
||
|
||
[ObservableObject]
|
||
public partial class ScriptRepoWindow
|
||
{
|
||
// 更新渠道类
|
||
public class RepoChannel
|
||
{
|
||
public string Name { get; set; }
|
||
public string Url { get; set; }
|
||
|
||
public RepoChannel(string name, string url)
|
||
{
|
||
Name = name;
|
||
Url = url;
|
||
}
|
||
}
|
||
|
||
// 渠道列表
|
||
private ObservableCollection<RepoChannel> _repoChannels;
|
||
public ObservableCollection<RepoChannel> RepoChannels => _repoChannels;
|
||
|
||
// 选中的渠道
|
||
[ObservableProperty] private RepoChannel? _selectedRepoChannel;
|
||
|
||
// 控制仓库地址是否只读
|
||
[ObservableProperty] private bool _isRepoUrlReadOnly = true;
|
||
|
||
// 添加进度相关的可观察属性
|
||
[ObservableProperty] private bool _isUpdating;
|
||
[ObservableProperty] private bool _isProgressIndeterminate;
|
||
[ObservableProperty] private int _updateProgressValue;
|
||
[ObservableProperty] private string _updateProgressText = "准备更新,请耐心等待...";
|
||
[ObservableProperty] private ScriptConfig _config = TaskContext.Instance().Config.ScriptConfig;
|
||
|
||
// Git 凭据相关属性
|
||
private const string GitCredentialAppName = "BetterGenshinImpact.GitCredentials";
|
||
|
||
[ObservableProperty] private string _gitUsername = "";
|
||
[ObservableProperty] private string _gitToken = "";
|
||
|
||
// 在线更新相关属性
|
||
[ObservableProperty] private string _onlineDownloadUrl = "";
|
||
|
||
// 获取当前仓库URL(用于界面显示)
|
||
public string CurrentRepoUrl
|
||
{
|
||
get
|
||
{
|
||
if (SelectedRepoChannel == null)
|
||
{
|
||
return "";
|
||
}
|
||
return SelectedRepoChannel.Name == "自定义" ? Config.CustomRepoUrl : SelectedRepoChannel.Url;
|
||
}
|
||
}
|
||
|
||
public ScriptRepoWindow()
|
||
{
|
||
InitializeRepoChannels();
|
||
LoadCredentialsFromManager();
|
||
InitializeComponent();
|
||
DataContext = this;
|
||
Config.PropertyChanged += OnConfigPropertyChanged;
|
||
PropertyChanged += OnPropertyChanged;
|
||
ScriptRepoUpdater.Instance.AutoUpdateStateChanged += OnAutoUpdateStateChanged;
|
||
|
||
// 设置 PasswordBox 的初始值
|
||
Loaded += (s, e) =>
|
||
{
|
||
GitTokenPasswordBox.Password = GitToken;
|
||
SyncAutoUpdateState();
|
||
};
|
||
|
||
SourceInitialized += (s, e) =>
|
||
{
|
||
// 应用系统背景
|
||
WindowHelper.TryApplySystemBackdrop(this);
|
||
|
||
// 设置仓库地址的只读状态
|
||
IsRepoUrlReadOnly = SelectedRepoChannel == null || SelectedRepoChannel.Name != "自定义";
|
||
};
|
||
}
|
||
|
||
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||
{
|
||
//OnSelectedRepoChannelChanged
|
||
if (e.PropertyName == nameof(SelectedRepoChannel))
|
||
{
|
||
OnSelectedRepoChannelChanged();
|
||
}
|
||
// 监听IsUpdating变化以调整窗口高度
|
||
else if (e.PropertyName == nameof(IsUpdating))
|
||
{
|
||
OnIsUpdatingChanged();
|
||
}
|
||
// 监听 GitUsername 和 GitToken 变化,保存到凭据管理器
|
||
else if (e.PropertyName == nameof(GitUsername) || e.PropertyName == nameof(GitToken))
|
||
{
|
||
SaveCredentialsToManager();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 Windows 凭据管理器加载 Git 凭据
|
||
/// </summary>
|
||
private void LoadCredentialsFromManager()
|
||
{
|
||
var credential = CredentialManagerHelper.ReadCredential(GitCredentialAppName);
|
||
GitUsername = credential?.UserName ?? "";
|
||
GitToken = credential?.Password ?? "";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存 Git 凭据到 Windows 凭据管理器
|
||
/// </summary>
|
||
private void SaveCredentialsToManager()
|
||
{
|
||
CredentialManagerHelper.SaveCredential(
|
||
GitCredentialAppName,
|
||
GitUsername,
|
||
GitToken,
|
||
"Git credentials for BetterGenshinImpact script repository",
|
||
CredentialPersistence.LocalMachine);
|
||
}
|
||
|
||
~ScriptRepoWindow()
|
||
{
|
||
Config.PropertyChanged -= OnConfigPropertyChanged;
|
||
PropertyChanged -= OnPropertyChanged;
|
||
ScriptRepoUpdater.Instance.AutoUpdateStateChanged -= OnAutoUpdateStateChanged;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 后台自动更新状态变化回调(可能在非 UI 线程触发)
|
||
/// </summary>
|
||
private void OnAutoUpdateStateChanged(object? sender, EventArgs e)
|
||
{
|
||
Dispatcher.BeginInvoke(SyncAutoUpdateState);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 同步后台自动更新状态到 Dialog 的进度条和按钮
|
||
/// </summary>
|
||
private void SyncAutoUpdateState()
|
||
{
|
||
if (ScriptRepoUpdater.Instance.IsAutoUpdating)
|
||
{
|
||
IsUpdating = true;
|
||
IsProgressIndeterminate = true;
|
||
UpdateProgressText = "后台正在自动更新订阅脚本...";
|
||
Toast.Information("后台正在自动更新订阅脚本,请等待完成");
|
||
}
|
||
else
|
||
{
|
||
IsUpdating = false;
|
||
IsProgressIndeterminate = false;
|
||
}
|
||
}
|
||
|
||
private void OnConfigPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||
{
|
||
// 监听CustomRepoUrl变化,通知界面更新显示
|
||
if (e.PropertyName == nameof(ScriptConfig.CustomRepoUrl))
|
||
{
|
||
OnPropertyChanged(nameof(CurrentRepoUrl));
|
||
}
|
||
}
|
||
|
||
private void OnIsUpdatingChanged()
|
||
{
|
||
// 当IsUpdating状态变化时,强制重新计算窗口大小
|
||
Dispatcher.BeginInvoke(() =>
|
||
{
|
||
InvalidateMeasure();
|
||
UpdateLayout();
|
||
}, System.Windows.Threading.DispatcherPriority.Loaded);
|
||
}
|
||
|
||
private void InitializeRepoChannels()
|
||
{
|
||
_repoChannels = new ObservableCollection<RepoChannel>(
|
||
ScriptRepoUpdater.RepoChannels.Select(kv => new RepoChannel(kv.Key, kv.Value))
|
||
);
|
||
_repoChannels.Add(new RepoChannel("自定义", "https://example.com/custom-repo"));
|
||
|
||
// 根据配置中保存的渠道名称恢复选择
|
||
if (string.IsNullOrEmpty(Config.SelectedChannelName))
|
||
{
|
||
// 默认选中第一个渠道
|
||
SelectedRepoChannel = _repoChannels[0];
|
||
Config.SelectedChannelName = SelectedRepoChannel.Name;
|
||
}
|
||
else
|
||
{
|
||
// 根据保存的渠道名称找到对应的渠道
|
||
var savedChannel = _repoChannels.FirstOrDefault(c => c.Name == Config.SelectedChannelName);
|
||
SelectedRepoChannel = savedChannel ?? _repoChannels[0];
|
||
|
||
// 如果找不到保存的渠道,更新配置为默认渠道
|
||
if (savedChannel == null)
|
||
{
|
||
Config.SelectedChannelName = _repoChannels[0].Name;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void OnSelectedRepoChannelChanged()
|
||
{
|
||
if (SelectedRepoChannel is null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 保存选择的渠道名称
|
||
Config.SelectedChannelName = SelectedRepoChannel.Name;
|
||
|
||
// 更新仓库地址只读状态
|
||
IsRepoUrlReadOnly = SelectedRepoChannel.Name != "自定义";
|
||
|
||
// 通知界面更新CurrentRepoUrl
|
||
OnPropertyChanged(nameof(CurrentRepoUrl));
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task UpdateRepo()
|
||
{
|
||
if (SelectedRepoChannel is null)
|
||
{
|
||
Toast.Warning("请选择一个脚本仓库更新渠道。");
|
||
return;
|
||
}
|
||
|
||
// 获取当前仓库URL
|
||
string repoUrl = CurrentRepoUrl;
|
||
|
||
// 验证URL
|
||
if (string.IsNullOrWhiteSpace(repoUrl))
|
||
{
|
||
Toast.Warning("请输入自定义仓库URL。");
|
||
return;
|
||
}
|
||
|
||
if (repoUrl == "https://example.com/custom-repo")
|
||
{
|
||
Toast.Warning("请修改默认的自定义URL为有效的仓库地址。");
|
||
return;
|
||
}
|
||
|
||
if (!Uri.TryCreate(repoUrl, UriKind.Absolute, out _))
|
||
{
|
||
Toast.Warning("请输入有效的URL地址。");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 显示更新中提示
|
||
Toast.Information("正在更新脚本仓库,请耐心等待...");
|
||
|
||
// 设置进度显示
|
||
IsUpdating = true;
|
||
IsProgressIndeterminate = true;
|
||
UpdateProgressValue = 0;
|
||
UpdateProgressText = "准备更新,请耐心等待...";
|
||
|
||
// 执行更新(Task.Run 避免 SynchronizationContext 将后续 IO 调度回 UI 线程)
|
||
var (_, updated) = await Task.Run(() => ScriptRepoUpdater.Instance.UpdateCenterRepoByGit(repoUrl,
|
||
(path, steps, totalSteps) =>
|
||
{
|
||
// 收到实际进度后切换为确定模式
|
||
IsProgressIndeterminate = false;
|
||
// 更新进度显示(WPF 绑定引擎会自动将跨线程 PropertyChanged 调度到 UI 线程)
|
||
double progressPercentage = totalSteps > 0 ? Math.Min(100, (double)steps / totalSteps * 100) : 0;
|
||
UpdateProgressValue = (int)progressPercentage;
|
||
UpdateProgressText = $"{path}";
|
||
}));
|
||
|
||
// 更新结果提示
|
||
if (updated)
|
||
{
|
||
Toast.Success("脚本仓库更新成功,有新内容");
|
||
}
|
||
else
|
||
{
|
||
Toast.Success("脚本仓库已是最新");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
await ThemedMessageBox.ErrorAsync($"更新失败,可尝试重置仓库后重新更新。失败原因:{ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
// 隐藏进度条
|
||
IsUpdating = false;
|
||
IsProgressIndeterminate = false;
|
||
}
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task OpenLocalScriptRepo()
|
||
{
|
||
// 检查是否需要提示用户更新仓库
|
||
var shouldContinue = await CheckAndPromptRepoUpdate();
|
||
if (shouldContinue)
|
||
{
|
||
TaskContext.Instance().Config.ScriptConfig.ScriptRepoHintDotVisible = false;
|
||
ScriptRepoUpdater.Instance.OpenLocalRepoInWebView();
|
||
Close();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查仓库更新时间并提示用户
|
||
/// </summary>
|
||
/// <returns>是否继续打开仓库(true: 继续打开, false: 取消操作)</returns>
|
||
private async Task<bool> CheckAndPromptRepoUpdate()
|
||
{
|
||
TimeSpan timeSinceUpdate;
|
||
try
|
||
{
|
||
// 检查仓库文件夹是否存在
|
||
if (!Directory.Exists(ScriptRepoUpdater.CenterRepoPath))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
// 查找 repo.json 文件
|
||
var repoJsonPath = Directory.GetFiles(ScriptRepoUpdater.CenterRepoPath, "repo.json", SearchOption.AllDirectories).FirstOrDefault();
|
||
if (repoJsonPath == null || !File.Exists(repoJsonPath))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
// 获取 repo.json 文件的最后修改时间
|
||
var repoJsonFile = new FileInfo(repoJsonPath);
|
||
DateTime lastUpdateTime = repoJsonFile.LastWriteTime;
|
||
|
||
// 检查是否超过 30 天
|
||
timeSinceUpdate = DateTime.Now - lastUpdateTime;
|
||
if (timeSinceUpdate.TotalDays <= 30)
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 出现异常时,继续打开仓库
|
||
return true;
|
||
}
|
||
|
||
// 提示用户更新
|
||
var dialog = new RepoUpdateDialog((int)timeSinceUpdate.TotalDays);
|
||
var result = await dialog.ShowDialogAsync();
|
||
|
||
if (result == Wpf.Ui.Controls.MessageBoxResult.Primary)
|
||
{
|
||
// 用户选择"立即更新"
|
||
await UpdateRepo();
|
||
return false;
|
||
}
|
||
else if (result == Wpf.Ui.Controls.MessageBoxResult.Secondary)
|
||
{
|
||
// 用户选择"直接打开"
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
// 用户关闭对话框(点击 X 或按 ESC)
|
||
return false;
|
||
}
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task ResetRepo()
|
||
{
|
||
if (IsUpdating)
|
||
{
|
||
Toast.Warning("请等待当前更新完成后再进行重置操作。");
|
||
return;
|
||
}
|
||
|
||
// 添加确认对话框
|
||
var result = await ThemedMessageBox.ShowAsync(
|
||
"确定要重置脚本仓库吗?无法正常更新时候可以使用本功能,重置后请重新更新脚本仓库。",
|
||
"确认重置",
|
||
MessageBoxButton.YesNo,
|
||
ThemedMessageBox.MessageBoxIcon.Warning);
|
||
|
||
if (result == MessageBoxResult.Yes)
|
||
{
|
||
try
|
||
{
|
||
await ScriptRepoUpdater.Instance.ResetCurrentRepoAsync();
|
||
Toast.Success("脚本仓库已重置,请重新更新脚本仓库。");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Toast.Error($"重置失败: {ex.Message}");
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
[RelayCommand]
|
||
private async Task DownloadOnlineRepo()
|
||
{
|
||
if (string.IsNullOrWhiteSpace(OnlineDownloadUrl))
|
||
{
|
||
Toast.Warning("请输入有效的下载地址。");
|
||
return;
|
||
}
|
||
|
||
if (IsUpdating)
|
||
{
|
||
Toast.Warning("请等待当前操作完成后再进行下载。");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
IsUpdating = true;
|
||
UpdateProgressValue = 0;
|
||
UpdateProgressText = "正在下载脚本仓库...";
|
||
|
||
using var httpClient = new HttpClient();
|
||
httpClient.Timeout = TimeSpan.FromMinutes(10);
|
||
|
||
// 下载文件
|
||
var response = await httpClient.GetAsync(OnlineDownloadUrl);
|
||
response.EnsureSuccessStatusCode();
|
||
|
||
var tempZipPath = Path.Combine(Path.GetTempPath(), $"script_repo_{DateTime.Now:yyyyMMddHHmmss}.zip");
|
||
await using (var fileStream = File.Create(tempZipPath))
|
||
{
|
||
await response.Content.CopyToAsync(fileStream);
|
||
}
|
||
|
||
UpdateProgressText = "正在解压并导入...";
|
||
UpdateProgressValue = 50;
|
||
|
||
// 导入下载的zip文件
|
||
await ImportZipFile(tempZipPath);
|
||
|
||
// 清理临时文件
|
||
if (File.Exists(tempZipPath))
|
||
{
|
||
File.Delete(tempZipPath);
|
||
}
|
||
|
||
Toast.Success("在线脚本仓库下载并导入成功!");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Toast.Error($"下载失败: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
IsUpdating = false;
|
||
}
|
||
}*/
|
||
|
||
[RelayCommand]
|
||
private async Task ImportLocalScriptsRepoZip()
|
||
{
|
||
if (IsUpdating)
|
||
{
|
||
Toast.Warning("请等待当前操作完成后再进行导入。");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
var openFileDialog = new OpenFileDialog
|
||
{
|
||
Title = "选择脚本仓库压缩包",
|
||
Filter = "压缩包文件 (*.zip)|*.zip",
|
||
Multiselect = false
|
||
};
|
||
|
||
if (openFileDialog.ShowDialog() == true)
|
||
{
|
||
IsUpdating = true;
|
||
IsProgressIndeterminate = false; // 导入有实际百分比进度
|
||
UpdateProgressValue = 0;
|
||
UpdateProgressText = "正在导入脚本仓库,请耐心等待...";
|
||
|
||
await ScriptRepoUpdater.Instance.ImportLocalRepoZip(openFileDialog.FileName, (progress, text) =>
|
||
{
|
||
Dispatcher.Invoke(() =>
|
||
{
|
||
UpdateProgressValue = progress;
|
||
UpdateProgressText = text;
|
||
});
|
||
});
|
||
|
||
Toast.Success("脚本仓库导入成功!");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Toast.Error($"导入失败: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
IsUpdating = false;
|
||
}
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task UpdateSubscribedScripts()
|
||
{
|
||
if (IsUpdating)
|
||
{
|
||
Toast.Warning("请等待当前操作完成后再进行更新。");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
IsUpdating = true;
|
||
IsProgressIndeterminate = true;
|
||
UpdateProgressValue = 0;
|
||
UpdateProgressText = "正在更新订阅脚本...";
|
||
|
||
// Task.Run 避免 SynchronizationContext 将 checkout/IO 调度回 UI 线程
|
||
await Task.Run(() => ScriptRepoUpdater.Instance.ManualUpdateSubscribedScripts());
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Toast.Error($"更新订阅脚本失败: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
IsUpdating = false;
|
||
IsProgressIndeterminate = false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理超链接点击事件
|
||
/// </summary>
|
||
/// <param name="sender"></param>
|
||
/// <param name="e"></param>
|
||
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
|
||
{
|
||
try
|
||
{
|
||
Process.Start(new ProcessStartInfo
|
||
{
|
||
FileName = e.Uri.AbsoluteUri,
|
||
UseShellExecute = true
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
ThemedMessageBox.Warning($"无法打开链接: {ex.Message}", "错误");
|
||
}
|
||
e.Handled = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理 PasswordBox 的密码变化事件
|
||
/// </summary>
|
||
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
|
||
{
|
||
if (sender is System.Windows.Controls.PasswordBox passwordBox)
|
||
{
|
||
// 更新 GitToken 属性,触发自动保存到凭据管理器
|
||
GitToken = passwordBox.Password;
|
||
}
|
||
}
|
||
} |