mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-25 10:05:49 +08:00
feat: 根据文件夹名字和内容重合度区分仓库;启动时自动更新仓库和订阅 (#2767)
* 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: 修复按钮位置
This commit is contained in:
@@ -53,6 +53,7 @@ public partial class ScriptRepoWindow
|
||||
|
||||
// 添加进度相关的可观察属性
|
||||
[ObservableProperty] private bool _isUpdating;
|
||||
[ObservableProperty] private bool _isProgressIndeterminate;
|
||||
[ObservableProperty] private int _updateProgressValue;
|
||||
[ObservableProperty] private string _updateProgressText = "准备更新,请耐心等待...";
|
||||
[ObservableProperty] private ScriptConfig _config = TaskContext.Instance().Config.ScriptConfig;
|
||||
@@ -87,9 +88,14 @@ public partial class ScriptRepoWindow
|
||||
DataContext = this;
|
||||
Config.PropertyChanged += OnConfigPropertyChanged;
|
||||
PropertyChanged += OnPropertyChanged;
|
||||
ScriptRepoUpdater.Instance.AutoUpdateStateChanged += OnAutoUpdateStateChanged;
|
||||
|
||||
// 设置 PasswordBox 的初始值
|
||||
Loaded += (s, e) => GitTokenPasswordBox.Password = GitToken;
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
GitTokenPasswordBox.Password = GitToken;
|
||||
SyncAutoUpdateState();
|
||||
};
|
||||
|
||||
SourceInitialized += (s, e) =>
|
||||
{
|
||||
@@ -147,6 +153,34 @@ public partial class 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)
|
||||
@@ -170,13 +204,10 @@ public partial class ScriptRepoWindow
|
||||
|
||||
private void InitializeRepoChannels()
|
||||
{
|
||||
_repoChannels = new ObservableCollection<RepoChannel>
|
||||
{
|
||||
new("CNB", "https://cnb.cool/bettergi/bettergi-scripts-list"),
|
||||
new("GitCode", "https://gitcode.com/huiyadanli/bettergi-scripts-list"),
|
||||
new("GitHub", "https://github.com/babalae/bettergi-scripts-list"),
|
||||
new("自定义", "https://example.com/custom-repo")
|
||||
};
|
||||
_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))
|
||||
@@ -254,18 +285,21 @@ public partial class ScriptRepoWindow
|
||||
|
||||
// 设置进度显示
|
||||
IsUpdating = true;
|
||||
IsProgressIndeterminate = true;
|
||||
UpdateProgressValue = 0;
|
||||
UpdateProgressText = "准备更新,请耐心等待...";
|
||||
|
||||
// 执行更新
|
||||
var (_, updated) = await ScriptRepoUpdater.Instance.UpdateCenterRepoByGit(repoUrl,
|
||||
// 执行更新(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)
|
||||
@@ -285,6 +319,7 @@ public partial class ScriptRepoWindow
|
||||
{
|
||||
// 隐藏进度条
|
||||
IsUpdating = false;
|
||||
IsProgressIndeterminate = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,15 +417,8 @@ public partial class ScriptRepoWindow
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(ScriptRepoUpdater.CenterRepoPath))
|
||||
{
|
||||
DirectoryHelper.DeleteReadOnlyDirectory(ScriptRepoUpdater.CenterRepoPath);
|
||||
Toast.Success("脚本仓库已重置,请重新更新脚本仓库。");
|
||||
}
|
||||
else
|
||||
{
|
||||
Toast.Information("脚本仓库不存在,无需重置");
|
||||
}
|
||||
await ScriptRepoUpdater.Instance.ResetCurrentRepoAsync();
|
||||
Toast.Success("脚本仓库已重置,请重新更新脚本仓库。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -479,10 +507,19 @@ public partial class ScriptRepoWindow
|
||||
if (openFileDialog.ShowDialog() == true)
|
||||
{
|
||||
IsUpdating = true;
|
||||
IsProgressIndeterminate = false; // 导入有实际百分比进度
|
||||
UpdateProgressValue = 0;
|
||||
UpdateProgressText = "正在导入脚本仓库,请耐心等待...";
|
||||
|
||||
await ImportZipFile(openFileDialog.FileName);
|
||||
await ScriptRepoUpdater.Instance.ImportLocalRepoZip(openFileDialog.FileName, (progress, text) =>
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
UpdateProgressValue = progress;
|
||||
UpdateProgressText = text;
|
||||
});
|
||||
});
|
||||
|
||||
Toast.Success("脚本仓库导入成功!");
|
||||
}
|
||||
}
|
||||
@@ -496,93 +533,34 @@ public partial class ScriptRepoWindow
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ImportZipFile(string zipFilePath)
|
||||
[RelayCommand]
|
||||
private async Task UpdateSubscribedScripts()
|
||||
{
|
||||
await Task.Run(() =>
|
||||
if (IsUpdating)
|
||||
{
|
||||
var tempPath = ScriptRepoUpdater.ReposTempPath;
|
||||
try
|
||||
{
|
||||
// 阶段1: 准备工作 (0-10%)
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
UpdateProgressValue = 0;
|
||||
UpdateProgressText = "正在准备导入环境...";
|
||||
});
|
||||
Toast.Warning("请等待当前操作完成后再进行更新。");
|
||||
return;
|
||||
}
|
||||
|
||||
var tempUnzipDir = Path.Combine(tempPath, "importZipFile");
|
||||
|
||||
// 先删除临时目录
|
||||
DirectoryHelper.DeleteReadOnlyDirectory(tempPath);
|
||||
|
||||
// 创建目标目录
|
||||
Directory.CreateDirectory(tempUnzipDir);
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
UpdateProgressValue = 10;
|
||||
UpdateProgressText = "准备完成,开始解压文件...";
|
||||
});
|
||||
|
||||
// 阶段2: 解压zip文件 (10-50%)
|
||||
ZipFile.ExtractToDirectory(zipFilePath, tempUnzipDir, true);
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
UpdateProgressValue = 50;
|
||||
UpdateProgressText = "文件解压完成,正在验证仓库结构...";
|
||||
});
|
||||
|
||||
// 阶段3: 查找并验证 repo.json (50-60%)
|
||||
var repoJsonPath = Directory.GetFiles(tempUnzipDir, "repo.json", SearchOption.AllDirectories).FirstOrDefault();
|
||||
if (repoJsonPath == null)
|
||||
{
|
||||
throw new FileNotFoundException("未找到 repo.json 文件,导入失败。");
|
||||
}
|
||||
|
||||
var repoDir = Path.GetDirectoryName(repoJsonPath)!;
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
UpdateProgressValue = 60;
|
||||
UpdateProgressText = "仓库结构验证通过,正在清理旧数据...";
|
||||
});
|
||||
|
||||
// 阶段4: 删除旧的目标目录 (60-70%)
|
||||
if (Directory.Exists(ScriptRepoUpdater.CenterRepoPath))
|
||||
{
|
||||
DirectoryHelper.DeleteReadOnlyDirectory(ScriptRepoUpdater.CenterRepoPath);
|
||||
}
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
UpdateProgressValue = 70;
|
||||
UpdateProgressText = "旧数据清理完成,正在复制新仓库...";
|
||||
});
|
||||
|
||||
// 阶段5: 复制新目录 (70-95%)
|
||||
DirectoryHelper.CopyDirectory(repoDir, ScriptRepoUpdater.CenterRepoPath);
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
UpdateProgressValue = 95;
|
||||
UpdateProgressText = "仓库复制完成,正在清理临时文件...";
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 阶段6: 清理临时文件 (95-100%)
|
||||
DirectoryHelper.DeleteReadOnlyDirectory(tempPath);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// 最终完成
|
||||
Dispatcher.Invoke(() =>
|
||||
try
|
||||
{
|
||||
UpdateProgressValue = 100;
|
||||
UpdateProgressText = "导入完成";
|
||||
});
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user