12 Commits

Author SHA1 Message Date
218e1456f2 将临时目录移动到程序执行位置 2025-08-22 22:55:26 +08:00
734d3bb348 修正文件名称 2025-08-07 04:43:49 +08:00
b294c96687 移除临时文件备份 2025-07-20 00:07:09 +08:00
9084f35b8f 文件移动问题 2025-07-19 18:24:19 +08:00
6d12a89c9c 自身更新 2025-07-19 17:58:38 +08:00
848de6f3fa 修改了结束exe程序的位置 2025-07-18 17:22:23 +08:00
9b790fe9e0 将移动临时文件修改为复制临时文件 2025-07-18 17:10:55 +08:00
f950b05b62 移除清理临时文件的方法 2025-07-18 16:10:22 +08:00
a3bfee2755 7zdll目录修改 2025-07-03 12:46:26 +08:00
qinsi_travel
e3b32ed453 删除配置 2025-07-02 21:27:28 +08:00
qinsi_travel
0891d7534a 恢复密钥 2025-07-02 21:23:36 +08:00
ef9aa7bc5e 优化 2025-07-02 15:50:05 +08:00
6 changed files with 607 additions and 488 deletions

View File

View File

@@ -137,16 +137,16 @@
<Reference Include="System.Configuration" /> <Reference Include="System.Configuration" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Form1.cs"> <Compile Include="Update.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
<Compile Include="Form1.Designer.cs"> <Compile Include="Update.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon> <DependentUpon>Update.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Form1.resx"> <EmbeddedResource Include="Update.resx">
<DependentUpon>Form1.cs</DependentUpon> <DependentUpon>Update.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx"> <EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator> <Generator>ResXFileCodeGenerator</Generator>
@@ -184,22 +184,23 @@
</BootstrapperPackage> </BootstrapperPackage>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="7z-x86.dll" /> <Content Include="7z.dll">
<EmbeddedResource Include="7z-x64.dll" /> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup> <PropertyGroup>
<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用"NuGet 程序包还原"可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText> <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用"NuGet 程序包还原"可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
</PropertyGroup> </PropertyGroup>
<Error Condition="!Exists('packages\Fody.6.9.2\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Fody.6.9.2\build\Fody.targets'))" />
<Error Condition="!Exists('packages\Costura.Fody.6.0.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.6.0.0\build\Costura.Fody.props'))" /> <Error Condition="!Exists('packages\Costura.Fody.6.0.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.6.0.0\build\Costura.Fody.props'))" />
<Error Condition="!Exists('packages\Costura.Fody.6.0.0\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.6.0.0\build\Costura.Fody.targets'))" /> <Error Condition="!Exists('packages\Costura.Fody.6.0.0\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.6.0.0\build\Costura.Fody.targets'))" />
<Error Condition="!Exists('packages\Fody.6.9.2\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Fody.6.9.2\build\Fody.targets'))" /> <!-- <Error Condition="!Exists('packages\SevenZipSharp.Interop.19.1.0\build\SevenZipSharp.Interop.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\SevenZipSharp.Interop.19.1.0\build\SevenZipSharp.Interop.targets'))" /> -->
<Error Condition="!Exists('packages\SevenZipSharp.Interop.19.1.0\build\SevenZipSharp.Interop.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\SevenZipSharp.Interop.19.1.0\build\SevenZipSharp.Interop.targets'))" /> <!-- <Error Condition="!Exists('packages\SevenZipExtractor.1.0.19\build\SevenZipExtractor.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\SevenZipExtractor.1.0.19\build\SevenZipExtractor.targets'))" /> -->
<Error Condition="!Exists('packages\SevenZipExtractor.1.0.19\build\SevenZipExtractor.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\SevenZipExtractor.1.0.19\build\SevenZipExtractor.targets'))" />
</Target> </Target>
<Import Project="packages\Costura.Fody.6.0.0\build\Costura.Fody.targets" Condition="Exists('packages\Costura.Fody.6.0.0\build\Costura.Fody.targets')" />
<Import Project="packages\Fody.6.9.2\build\Fody.targets" Condition="Exists('packages\Fody.6.9.2\build\Fody.targets')" /> <Import Project="packages\Fody.6.9.2\build\Fody.targets" Condition="Exists('packages\Fody.6.9.2\build\Fody.targets')" />
<Import Project="packages\SevenZipSharp.Interop.19.1.0\build\SevenZipSharp.Interop.targets" Condition="Exists('packages\SevenZipSharp.Interop.19.1.0\build\SevenZipSharp.Interop.targets')" /> <Import Project="packages\Costura.Fody.6.0.0\build\Costura.Fody.targets" Condition="Exists('packages\Costura.Fody.6.0.0\build\Costura.Fody.targets')" />
<Import Project="packages\SevenZipExtractor.1.0.19\build\SevenZipExtractor.targets" Condition="Exists('packages\SevenZipExtractor.1.0.19\build\SevenZipExtractor.targets')" /> <!-- <Import Project="packages\SevenZipSharp.Interop.19.1.0\build\SevenZipSharp.Interop.targets" Condition="Exists('packages\SevenZipSharp.Interop.19.1.0\build\SevenZipSharp.Interop.targets')" /> -->
<!-- <Import Project="packages\SevenZipExtractor.1.0.19\build\SevenZipExtractor.targets" Condition="Exists('packages\SevenZipExtractor.1.0.19\build\SevenZipExtractor.targets')" /> -->
</Project> </Project>

131
README.md
View File

@@ -32,8 +32,8 @@
## 用户界面 ## 用户界面
- **实时进度显示**:通过进度条、已完成数量/总数以及 **已下载总量(实时速度)** 的形式(两者均保留一位小数,每 0.5 秒刷新),清晰地展示更新进度。 - **实时进度显示**:通过进度条、已完成数量/总数以及 **已下载总量(实时速度)** 的形式清晰地展示更新进度。已下载总量会包含已存在于临时目录的有效文件,速度则只计算运行时下载的部分
- **简洁的状态反馈**:界面只显示当前正在处理的文件名等核心信息,避免被冗长的日志刷屏。 - **简洁的状态反馈**:界面会以 `下载:文件名...` 的格式显示当前任务,过长的文件名会被自动截断,避免信息刷屏。
- **友好的错误提示**:当发生严重错误时,会弹出简明扼要的错误信息窗口,而不是难以理解的完整堆栈跟踪。 - **友好的错误提示**:当发生严重错误时,会弹出简明扼要的错误信息窗口,而不是难以理解的完整堆栈跟踪。
- **自动定位与退出**:窗体启动时会自动停靠在屏幕右下角,更新完成后会自动关闭,对用户干扰极小。 - **自动定位与退出**:窗体启动时会自动停靠在屏幕右下角,更新完成后会自动关闭,对用户干扰极小。
@@ -53,45 +53,43 @@
## 🚀 主要功能 ## 🚀 主要功能
### 📥 多源下载支持 ### 📥 多源下载支持
- **123盘云存储**: 支持主备域名自动切换,提供高可用性下载服务 - **123盘云存储**: (UID: `1826795402`, Path: `/1826795402/KeyAuth`) 支持主备域名自动切换,提供高可用性下载服务
- **阿里云OSS**: 备用下载源,确保文件下载的可靠性 - **阿里云OSS**: 备用下载源,确保文件下载的可靠性
- **智能DNS解析**: 使用多个DNS服务器提高域名解析成功率 - **智能DNS解析**: 使用多个DNS服务器提高域名解析成功率
### 🔄 智能更新机制 ### 🔄 智能更新机制
- **MD5完整性验证**: 下载前后进行MD5校验确保文件完整性 - **MD5完整性验证**: 下载前后进行MD5校验确保文件完整性
- **增量更新**: 仅下载已变更的文件,节省带宽和时间 - **增量更新**: 仅下载已变更的文件,节省带宽和时间
- **断点续传**: 支持网络中断后继续下载,提高下载成功率 - **断点续传支持**: 通过检查本地临时文件实现若文件已存在且MD5匹配则跳过下载。
- **并发下载**: 可配置的多线程并发下载,显著提升下载速度 - **并发下载**: 可配置的多线程并发下载,显著提升下载速度
### 🛡️ 文件处理与安全 ### 🛡️ 文件处理与安全
- **文件占用处理**: 智能检测并处理被占用的文件 - **文件占用处理**: 智能检测并处理被占用的文件,并创建延迟替换脚本。
- **批处理脚本**: 为被占用文件创建延迟替换脚本 - **临时文件管理**: 自动清理临时文件,保持系统整洁。
- **临时文件管理**: 自动清理临时文件,保持系统整洁 - **路径智能识别**: 基于MD5数据自动识别项目基准目录。
- **路径智能识别**: 基于MD5数据自动识别项目基准目录
### 📦 自动解压功能 ### 📦 自动解压功能
- **7z格式支持**: 内置7z解压引擎支持多种压缩格式 - **7z格式支持**: 内置7z解压引擎支持多种压缩格式
- **自动权限设置**: 解压后自动为exe文件设置管理员运行权限 - **自动权限设置**: 解压后自动为exe文件设置管理员运行权限
- **覆盖解压**: 支持覆盖模式解压,确保文件更新 - **多架构兼容**: 自动选择32位/64位解压库适配不同运行环境。
- **多架构兼容**: 自动选择32位/64位解压库适配不同运行环境
### 🔧 错误处理与重试 ### 🔧 错误处理与重试
- **多次重试机制**: 下载失败自动重试,可配置重试次数 - **多次重试机制**: 下载失败自动重试,可配置重试次数
- **异常处理**: 完善的异常捕获和处理机制 - **异常处理**: 完善的异常捕获和处理机制
- **状态实时显示**: 实时显示下载进度、状态和错误信息 - **状态实时显示**: 实时显示下载进度、状态和错误信息
## 🏗️ 技术架构 ## 🏗️ 技术架构
### 核心组件 ### 核心组件
- **.NET Framework 4.7.2**: 基于稳定的.NET Framework构建 - **.NET Framework**: 基于稳定的.NET Framework构建
- **异步编程**: 全面采用async/await模式确保UI响应性 - **异步编程**: 全面采用async/await模式确保UI响应性
- **多线程下载**: 使用SemaphoreSlim控制并发数量 - **多线程下载**: 使用SemaphoreSlim控制并发数量
- **资源嵌入**: 将依赖库嵌入程序,实现单文件部署 - **资源嵌入**: 将依赖库嵌入程序,实现单文件部署
### 依赖库 ### 依赖库
- **Aliyun.OSS.SDK**: 阿里云对象存储服务支持 - **Aliyun.OSS.SDK**: 阿里云对象存储服务支持
- **Newtonsoft.Json**: JSON数据处理 - **Newtonsoft.Json**: JSON数据处理
- **SevenZipExtractor**: 7z压缩文件解压支持 - **SevenZipExtractor**: 7z压缩文件解压支持
## 📋 配置说明 ## 📋 配置说明
@@ -115,80 +113,29 @@
"version": "1.0.0", "version": "1.0.0",
"data": { "data": {
"program.exe": "5d41402abc4b2a76b9719d911017c592", "program.exe": "5d41402abc4b2a76b9719d911017c592",
"lib/library.dll": "098f6bcd4621d373cade4e832627b4f6", "lib/library.dll": "098f6bcd4621d373cade4e832627b4f6"
"config/settings.ini": "5e40d4c123456789abcdef1234567890"
} }
} }
``` ```
## 🔄 工作流程 ## 🔄 工作流程
1. **启动检查**: 程序启动时清理旧的临时文件 1. **启动检查**: 程序启动时清理旧的更新文件 (`.new` 后缀)。
2. **下载MD5**: 从云存储下载最新的MD5文件 2. **下载MD5**: 从云存储下载最新的MD5文件
3. **文件比较**: 对比本地文件与在线MD5识别需要更新的文件 3. **文件比较**: 对比本地文件与在线MD5识别需要更新的文件
4. **并发下载**: 使用多线程下载需要更新的文件 4. **并发下载**: 使用多线程下载需要更新的文件到临时目录。
5. **完整性验证**: 验证下载文件的MD5值 5. **完整性验证**: 验证下载文件的MD5值
6. **文件替换**: 将新文件移动到目标位置 6. **文件替换**: 将新文件移动到目标位置,对被占用文件创建替换脚本。
7. **7z解压**: 自动检测并解压tim.7z文件 7. **7z解压**: 自动检测并解压`tim.7z`文件(如果存在)。
8. **权限设置**: 为解压的exe文件设置管理员运行权限 8. **权限设置**: 为解压的exe文件设置管理员运行权限
9. **清理完成**: 清理临时文件,显示完成状态 9. **清理完成**: 清理临时目录,显示完成状态后退出。
## 🎯 使用场景 ## 🎯 使用场景
- **软件自动更新**: 为桌面应用程序提供自动更新功能 - **软件自动更新**: 为桌面应用程序提供自动更新功能
- **文件同步**: 在不同设备间同步文件和配置 - **文件同步**: 在不同设备间同步文件和配置
- **批量部署**: 企业环境下的软件批量部署和更新 - **游戏更新**: 游戏客户端的增量更新和补丁分发。
- **游戏更新**: 游戏客户端的增量更新和补丁分发
## 🛠️ 开发特性
### 网络优化
- **静态HttpClient**: 避免套接字耗尽问题
- **连接池复用**: 提高网络请求效率
- **超时控制**: 合理的超时设置,避免长时间等待
### 内存管理
- **流式处理**: 大文件下载使用流式处理,控制内存占用
- **及时释放**: 及时释放不再使用的资源
- **临时文件**: 合理使用临时文件,避免内存溢出
### 用户体验
- **进度显示**: 实时显示下载进度和文件信息
- **状态反馈**: 详细的状态信息和错误提示
- **窗口定位**: 智能定位到屏幕右下角,不影响用户操作
## 📈 性能特点
- **高效下载**: 多线程并发下载,充分利用网络带宽
- **智能重试**: 失败文件智能重试,提高成功率
- **资源节约**: 仅下载变更文件,节省网络流量
- **快速启动**: 优化的启动流程,快速进入工作状态
## 🔒 安全性
- **MD5验证**: 确保文件在传输过程中没有被篡改
- **用户权限**: 在当前用户权限下操作,避免权限滥用
- **临时目录**: 使用用户临时目录,避免权限问题
- **异常处理**: 完善的异常处理,避免程序崩溃
## 📝 版本历史
### 最新版本特性
- ✅ 新增"已下载总量 + 实时下载速度"显示,速度与大小均保留一位小数,避免大文件下载时 UI 停滞误判。
- ✅ 添加7z自动解压功能
- ✅ 支持解压后程序自动设置管理员权限
- ✅ 优化多线程下载性能
- ✅ 增强错误处理和重试机制
- ✅ 改进用户界面和状态显示
- ✅ 过滤 .db / .db3 数据库文件,避免占用导致失败
- ✅ 更新列表或压缩包中出现 tim.dll 时,自动结束 tim.exe 后替换
- ✅ 所有文件匹配与进程检测均采用大小写不敏感逻辑,避免大小写差异造成的更新失败
- ✅ 精简 Status_Box 文本,并在失败时弹窗展示核心错误信息
## 🤝 技术支持
如需技术支持或报告问题,请联系开发团队。
--- ---
**注意**: 本程序需要网络连接以下载更新文件。首次运行时可能需要较长时间来下载必要的文件。 **注意**: 本程序需要网络连接以下载更新文件。首次运行时可能需要较长时间来下载所有必要的文件。

View File

@@ -75,6 +75,15 @@ namespace CheckDownload
// 基准目录路径(用于文件更新的目标目录) // 基准目录路径(用于文件更新的目标目录)
private string _baseDirectory; private string _baseDirectory;
// 7z.dll 库文件路径
private readonly string _sevenZipDllPath;
// 应用程序名称
private readonly string _appName;
// 当前进程ID
private readonly int _currentProcessId;
// === 新增: 用于显示整体下载大小与速度 === // === 新增: 用于显示整体下载大小与速度 ===
private long _totalDownloadedBytes = 0; // 已下载总字节数 private long _totalDownloadedBytes = 0; // 已下载总字节数
private DateTime _downloadStartTime; // 下载开始时间 private DateTime _downloadStartTime; // 下载开始时间
@@ -82,15 +91,24 @@ namespace CheckDownload
private readonly object _speedLock = new object(); // 锁,用于多线程更新 UI private readonly object _speedLock = new object(); // 锁,用于多线程更新 UI
private long _bytesSinceLastSpeedCalc = 0; // 距离上次速度计算新增的字节数 private long _bytesSinceLastSpeedCalc = 0; // 距离上次速度计算新增的字节数
// 更新锁文件路径
private string _updateLockFilePath;
/// <summary> /// <summary>
/// 初始化窗体 /// 初始化窗体
/// </summary> /// </summary>
public Update() public Update()
{ {
InitializeComponent(); InitializeComponent();
// 设置 7z.dll 的路径为程序运行目录
_sevenZipDllPath = Path.Combine(Application.StartupPath, "7z.dll");
_appName = Assembly.GetExecutingAssembly().GetName().Name;
_currentProcessId = Process.GetCurrentProcess().Id;
_baseDirectory = Application.StartupPath; _baseDirectory = Application.StartupPath;
_updateLockFilePath = Path.Combine(_baseDirectory, "update.lock");
ConfigureProgressBar(); ConfigureProgressBar();
InitializeTempDirectory();
} }
/// <summary> /// <summary>
@@ -188,6 +206,26 @@ namespace CheckDownload
} }
finally finally
{ {
// 删除更新锁文件,让批处理脚本知道程序已退出
CleanupLockFile();
}
}
/// <summary>
/// 清理更新锁文件
/// </summary>
private void CleanupLockFile()
{
try
{
if (!string.IsNullOrEmpty(_updateLockFilePath) && File.Exists(_updateLockFilePath))
{
File.Delete(_updateLockFilePath);
}
}
catch
{
// 忽略清理锁文件时的错误
} }
} }
@@ -207,6 +245,7 @@ namespace CheckDownload
{ {
try try
{ {
InitializeTempDirectory();
CleanupNewFiles(); CleanupNewFiles();
UpdateStatus("下载在线MD5文件并读取..."); UpdateStatus("下载在线MD5文件并读取...");
UpdateCount(""); UpdateCount("");
@@ -246,7 +285,7 @@ namespace CheckDownload
UpdateProgressValue(100); UpdateProgressValue(100);
// 无需更新时清理临时文件夹 // 无需更新时清理临时文件夹
CleanupTempDirectory(); //CleanupTempDirectory();
// 显示更新完成并等待2秒 // 显示更新完成并等待2秒
UpdateStatus("更新完成"); UpdateStatus("更新完成");
@@ -296,7 +335,7 @@ namespace CheckDownload
if (_completedCount == 0 && orderedFileList.Count > 0) if (_completedCount == 0 && orderedFileList.Count > 0)
{ {
throw new Exception("所有文件下载失败。"); throw new Exception("UpdateFile: 所有文件下载失败。");
} }
await VerifyAndSaveAllFiles(); await VerifyAndSaveAllFiles();
@@ -304,7 +343,7 @@ namespace CheckDownload
await DecompressTim7zAsync(); await DecompressTim7zAsync();
// 校验和保存成功后清理临时目录 // 校验和保存成功后清理临时目录
CleanupTempDirectory(); //CleanupTempDirectory();
// 显示完成状态并退出 // 显示完成状态并退出
UpdateStatus("更新完成"); UpdateStatus("更新完成");
@@ -314,8 +353,7 @@ namespace CheckDownload
} }
catch (Exception ex) catch (Exception ex)
{ {
UpdateStatus("更新失败"); HandleError("文件更新主流程 (UpdateFile)", ex);
MessageBox.Show($"更新失败:\n{ex.ToString()}", "Update Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
await Task.Delay(3000); await Task.Delay(3000);
this.Close(); this.Close();
} }
@@ -370,8 +408,7 @@ namespace CheckDownload
} }
catch (Exception ex) when (ex is OssException || ex is JsonException) catch (Exception ex) when (ex is OssException || ex is JsonException)
{ {
UpdateStatus($"读取在线MD5文件失败: {ex.Message}"); throw new Exception($"ReadOnlineMd5File: 读取或解析在线MD5文件 '{filePath}' 失败。", ex);
return (null, null, null);
} }
} }
@@ -453,37 +490,6 @@ namespace CheckDownload
return differences; return differences;
} }
/// <summary>
/// 批量下载文件列表并进行MD5验证支持重试机制
/// </summary>
/// <param name="fileList">包含文件路径和期望MD5值的字典</param>
private async Task DownloadAndVerifyFiles(Dictionary<string, string> fileList)
{
_totalCount = fileList.Count;
_completedCount = 0;
_downloadedFiles.Clear();
var failedFiles = new ConcurrentDictionary<string, string>();
await PerformDownloads(fileList, failedFiles);
if (!failedFiles.IsEmpty)
{
UpdateStatus($"有 {failedFiles.Count} 个文件下载失败,开始重试...");
var stillFailing = await RetryFailedFilesAsync(new Dictionary<string, string>(failedFiles));
if (stillFailing.Any())
{
UpdateStatus($"重试后仍有 {stillFailing.Count} 个文件下载失败。");
}
}
if (_completedCount == 0 && fileList.Count > 0)
{
throw new Exception("所有文件下载失败。");
}
await VerifyAndSaveAllFiles();
}
/// <summary> /// <summary>
/// 使用信号量控制并发数量,执行多个文件的并发下载任务 /// 使用信号量控制并发数量,执行多个文件的并发下载任务
/// </summary> /// </summary>
@@ -509,7 +515,12 @@ namespace CheckDownload
int progress = (int)((double)_completedCount / _totalCount * 95); int progress = (int)((double)_completedCount / _totalCount * 95);
UpdateProgressValue(progress); UpdateProgressValue(progress);
UpdateStatus($"{Path.GetFileName(file.Key)}"); string fileName = Path.GetFileName(file.Key);
string truncatedFileName = TruncateString(fileName, 10);
if (!string.IsNullOrWhiteSpace(truncatedFileName))
{
UpdateStatus($"下载:{truncatedFileName}");
}
UpdateCount($"{_completedCount}/{_totalCount}"); UpdateCount($"{_completedCount}/{_totalCount}");
} }
else else
@@ -533,14 +544,9 @@ namespace CheckDownload
/// <returns>下载成功返回true否则返回false</returns> /// <returns>下载成功返回true否则返回false</returns>
private async Task<bool> AttemptDownloadAsync(string filePath, string expectedMd5) private async Task<bool> AttemptDownloadAsync(string filePath, string expectedMd5)
{ {
// kill exe if running
if (Path.GetExtension(filePath).Equals(".exe", StringComparison.OrdinalIgnoreCase))
{
KillProcessIfRunning(filePath);
}
string tempFilePath = Path.Combine(_tempDirectory, filePath); string tempFilePath = Path.Combine(_tempDirectory, filePath);
string fileName = Path.GetFileName(filePath); string fileName = Path.GetFileName(filePath);
string truncatedFileName = TruncateString(fileName, 10);
try try
{ {
@@ -549,9 +555,9 @@ namespace CheckDownload
return true; return true;
} }
if (!string.IsNullOrWhiteSpace(fileName)) if (!string.IsNullOrWhiteSpace(truncatedFileName))
{ {
UpdateStatus($"下载:{fileName}"); UpdateStatus($"下载:{truncatedFileName}");
} }
UpdateCount($"{_completedCount + 1}/{_totalCount}"); UpdateCount($"{_completedCount + 1}/{_totalCount}");
if (await DownloadFileFromOneDrive(filePath, expectedMd5, tempFilePath)) if (await DownloadFileFromOneDrive(filePath, expectedMd5, tempFilePath))
@@ -559,9 +565,9 @@ namespace CheckDownload
return true; return true;
} }
if (!string.IsNullOrWhiteSpace(fileName)) if (!string.IsNullOrWhiteSpace(truncatedFileName))
{ {
UpdateStatus($"下载:{fileName}"); UpdateStatus($"下载:{truncatedFileName}");
} }
UpdateCount($"{_completedCount + 1}/{_totalCount}"); UpdateCount($"{_completedCount + 1}/{_totalCount}");
string ossKey = $"File/{expectedMd5}"; string ossKey = $"File/{expectedMd5}";
@@ -582,9 +588,9 @@ namespace CheckDownload
} }
catch (Exception ex) when (ex is OssException || ex is WebException) catch (Exception ex) when (ex is OssException || ex is WebException)
{ {
if (!string.IsNullOrWhiteSpace(fileName)) if (!string.IsNullOrWhiteSpace(truncatedFileName))
{ {
UpdateStatus($"下载:{fileName}"); UpdateStatus($"下载:{truncatedFileName}");
} }
UpdateCount($"{_completedCount + 1}/{_totalCount}"); UpdateCount($"{_completedCount + 1}/{_totalCount}");
UpdateSize(""); UpdateSize("");
@@ -593,9 +599,9 @@ namespace CheckDownload
} }
catch (Exception ex) catch (Exception ex)
{ {
if (!string.IsNullOrWhiteSpace(fileName)) if (!string.IsNullOrWhiteSpace(truncatedFileName))
{ {
UpdateStatus($"下载异常: {fileName} - {ex.Message}"); UpdateStatus($"下载异常: {truncatedFileName} - {ex.Message}");
} }
return false; return false;
} }
@@ -614,6 +620,7 @@ namespace CheckDownload
{ {
if (!File.Exists(tempFilePath)) if (!File.Exists(tempFilePath))
{ {
//UpdateStatus($"[调试] 临时文件不存在: {fileName}");
return false; return false;
} }
@@ -635,6 +642,7 @@ namespace CheckDownload
{ {
var fileInfo = new FileInfo(tempFilePath); var fileInfo = new FileInfo(tempFilePath);
Interlocked.Add(ref _totalDownloadedBytes, fileInfo.Length); Interlocked.Add(ref _totalDownloadedBytes, fileInfo.Length);
//UpdateStatus($"[调试] 临时文件验证通过,大小: {fileInfo.Length} bytes - {fileName}");
} }
catch catch
{ {
@@ -644,10 +652,6 @@ namespace CheckDownload
} }
else else
{ {
if (!string.IsNullOrWhiteSpace(fileName))
{
UpdateStatus($"临时文件不完整,重新下载: {fileName}");
}
File.Delete(tempFilePath); File.Delete(tempFilePath);
return false; return false;
} }
@@ -656,7 +660,7 @@ namespace CheckDownload
{ {
if (!string.IsNullOrWhiteSpace(fileName)) if (!string.IsNullOrWhiteSpace(fileName))
{ {
UpdateStatus($"检查临时文件时出错,将重新下载: {fileName} - {ex.Message}"); UpdateStatus($"临时文件异常,删除文件: {fileName} - {ex.Message}");
} }
try try
{ {
@@ -694,7 +698,6 @@ namespace CheckDownload
UpdateSize(""); UpdateSize("");
var request = new HttpRequestMessage(HttpMethod.Get, authUrl) { Version = HttpVersion.Version11 }; var request = new HttpRequestMessage(HttpMethod.Get, authUrl) { Version = HttpVersion.Version11 };
request.Headers.Add("User-Agent", "CheckDownload/1.0");
using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)) using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{ {
@@ -737,7 +740,6 @@ namespace CheckDownload
UpdateSize(""); UpdateSize("");
var request = new HttpRequestMessage(HttpMethod.Get, authUrl) { Version = HttpVersion.Version11 }; var request = new HttpRequestMessage(HttpMethod.Get, authUrl) { Version = HttpVersion.Version11 };
request.Headers.Add("User-Agent", "CheckDownload/1.0");
using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)) using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{ {
@@ -783,7 +785,6 @@ namespace CheckDownload
string authUrl = GenerateAuthUrl($"http://{OneDriveMainDomain}{OneDrivePath}", fileName); string authUrl = GenerateAuthUrl($"http://{OneDriveMainDomain}{OneDrivePath}", fileName);
var request = new HttpRequestMessage(HttpMethod.Get, authUrl) { Version = HttpVersion.Version11 }; var request = new HttpRequestMessage(HttpMethod.Get, authUrl) { Version = HttpVersion.Version11 };
request.Headers.Add("User-Agent", "CheckDownload/1.0");
using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)) using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{ {
@@ -812,7 +813,6 @@ namespace CheckDownload
string authUrl = GenerateAuthUrl($"http://{OneDriveBackupDomain}{OneDrivePath}", fileName); string authUrl = GenerateAuthUrl($"http://{OneDriveBackupDomain}{OneDrivePath}", fileName);
var request = new HttpRequestMessage(HttpMethod.Get, authUrl) { Version = HttpVersion.Version11 }; var request = new HttpRequestMessage(HttpMethod.Get, authUrl) { Version = HttpVersion.Version11 };
request.Headers.Add("User-Agent", "CheckDownload/1.0");
using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)) using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{ {
@@ -883,7 +883,6 @@ namespace CheckDownload
{ {
var requestUri = $"http://{dnsServer}/resolve?name={domain}&type=1&short=1"; var requestUri = $"http://{dnsServer}/resolve?name={domain}&type=1&short=1";
var request = new HttpRequestMessage(HttpMethod.Get, requestUri) { Version = HttpVersion.Version11 }; var request = new HttpRequestMessage(HttpMethod.Get, requestUri) { Version = HttpVersion.Version11 };
request.Headers.Add("User-Agent", "CheckDownload/1.0");
request.Headers.Host = dnsServer; request.Headers.Host = dnsServer;
var response = await _httpClient.SendAsync(request); var response = await _httpClient.SendAsync(request);
@@ -916,6 +915,10 @@ namespace CheckDownload
UpdateStatus("正在校验文件..."); UpdateStatus("正在校验文件...");
UpdateCount(""); UpdateCount("");
UpdateSize(""); UpdateSize("");
// 新增:在开始校验前创建临时文件备份
//await CreateTempFileBackup();
var failedFiles = new ConcurrentBag<string>(); var failedFiles = new ConcurrentBag<string>();
var filesForScripting = new ConcurrentBag<(string original, string newFile)>(); var filesForScripting = new ConcurrentBag<(string original, string newFile)>();
@@ -932,9 +935,18 @@ namespace CheckDownload
string expectedMd5 = item.Value; string expectedMd5 = item.Value;
string tempFilePath = Path.Combine(_tempDirectory, relativePath); string tempFilePath = Path.Combine(_tempDirectory, relativePath);
// 添加调试信息:检查临时文件是否存在
if (!File.Exists(tempFilePath))
{
//UpdateStatus($"[调试] 临时文件在校验前消失: {relativePath}");
failedFiles.Add(relativePath);
return;
}
string actualMd5 = CalculateMD5FromFile(tempFilePath); string actualMd5 = CalculateMD5FromFile(tempFilePath);
if (actualMd5 != expectedMd5.ToLower()) if (actualMd5 != expectedMd5.ToLower())
{ {
//UpdateStatus($"[调试] 文件MD5校验失败: {relativePath}");
failedFiles.Add(relativePath); failedFiles.Add(relativePath);
return; return;
} }
@@ -946,13 +958,24 @@ namespace CheckDownload
Directory.CreateDirectory(localDir); Directory.CreateDirectory(localDir);
} }
if (!await TryMoveFileAsync(tempFilePath, localPath)) //UpdateStatus($"[调试] 开始复制文件: {relativePath}");
if (!await TryCopyFileAsync(tempFilePath, localPath)) // 改为使用复制方法
{ {
//UpdateStatus($"[调试] 直接复制失败,创建.new文件: {relativePath}");
string backupPath = localPath + ".new"; string backupPath = localPath + ".new";
File.Move(tempFilePath, backupPath); File.Copy(tempFilePath, backupPath, true); // 复制而非移动
filesForScripting.Add((localPath, backupPath)); filesForScripting.Add((localPath, backupPath));
} }
else
{
//UpdateStatus($"[调试] 文件复制成功: {relativePath}");
}
// 复制后再次检查临时文件是否还存在
if (!File.Exists(tempFilePath))
{
//UpdateStatus($"[调试] 警告:文件复制后临时文件消失: {relativePath}");
}
} }
finally finally
{ {
@@ -977,7 +1000,9 @@ namespace CheckDownload
if (failedFiles.Count > 0) if (failedFiles.Count > 0)
{ {
throw new Exception($"{failedFiles.Count}个文件校验失败"); string failedFileList = string.Join(", ", failedFiles.Take(3));
if (failedFiles.Count > 3) failedFileList += "...";
throw new Exception($"VerifyAndSaveAllFiles: {failedFiles.Count}个文件校验失败: {failedFileList}");
} }
else else
{ {
@@ -1038,42 +1063,126 @@ namespace CheckDownload
{ {
string batchFilePath = Path.Combine(_baseDirectory, "update_files.bat"); string batchFilePath = Path.Combine(_baseDirectory, "update_files.bat");
string processId = Process.GetCurrentProcess().Id.ToString(); string processId = Process.GetCurrentProcess().Id.ToString();
string processName = Process.GetCurrentProcess().ProcessName;
// 创建锁文件,程序退出前会删除它
File.WriteAllText(_updateLockFilePath, processId);
var batchContent = new StringBuilder(); var batchContent = new StringBuilder();
batchContent.AppendLine("@echo off"); batchContent.AppendLine("@echo off");
batchContent.AppendLine("chcp 65001 > nul"); batchContent.AppendLine("title Update Process");
batchContent.AppendLine(":check_process");
batchContent.AppendLine($"tasklist /FI \"PID eq {processId}\" 2>NUL | find /I \"{processId}\" >NUL");
batchContent.AppendLine("if %ERRORLEVEL% == 0 (");
batchContent.AppendLine(" timeout /t 1 /nobreak > NUL");
batchContent.AppendLine(" goto check_process");
batchContent.AppendLine(")");
batchContent.AppendLine("timeout /t 2 /nobreak > NUL");
// 简化的等待逻辑,避免复杂的计数器
batchContent.AppendLine("echo 等待程序退出...");
batchContent.AppendLine(":wait_loop");
// 优先检查锁文件
batchContent.AppendLine($"if not exist \"{_updateLockFilePath}\" goto process_check");
// 等待0.5秒
batchContent.AppendLine("ping 127.0.0.1 -n 1 -w 500 >nul");
batchContent.AppendLine("goto wait_loop");
batchContent.AppendLine(":process_check");
// 再次确认进程已退出
batchContent.AppendLine($"tasklist /FI \"PID eq {processId}\" 2>nul | find /I \"{processId}\" >nul");
batchContent.AppendLine("if %ERRORLEVEL% equ 0 (");
batchContent.AppendLine(" ping 127.0.0.1 -n 1 -w 500 >nul");
batchContent.AppendLine(" goto process_check");
batchContent.AppendLine(")");
// 额外等待确保完全退出
batchContent.AppendLine("ping 127.0.0.1 -n 1 -w 1000 >nul");
batchContent.AppendLine("echo 开始更新文件...");
// 简化的文件处理逻辑,避免复杂的标签
int fileIndex = 0;
foreach (var file in files) foreach (var file in files)
{ {
batchContent.AppendLine($"del \"{file.original}\" /f /q"); string fileName = Path.GetFileName(file.original);
batchContent.AppendLine($"move \"{file.newFile}\" \"{file.original}\""); batchContent.AppendLine($"echo 处理文件: {fileName}");
// 检查新文件是否存在
batchContent.AppendLine($"if not exist \"{file.newFile}\" (");
batchContent.AppendLine($" echo 错误: 新文件不存在 - {fileName}");
batchContent.AppendLine($" goto file_{fileIndex}_end");
batchContent.AppendLine($")");
// 删除原文件(如果存在)
batchContent.AppendLine($"if exist \"{file.original}\" (");
batchContent.AppendLine($" echo 删除原文件: {fileName}");
batchContent.AppendLine($" del \"{file.original}\" /f /q");
batchContent.AppendLine($")");
// 复制新文件
batchContent.AppendLine($"echo 复制新文件: {fileName}");
batchContent.AppendLine($"copy /Y \"{file.newFile}\" \"{file.original}\"");
// 验证复制是否成功
batchContent.AppendLine($"if exist \"{file.original}\" (");
batchContent.AppendLine($" echo 成功更新: {fileName}");
batchContent.AppendLine($" del \"{file.newFile}\" /f /q");
batchContent.AppendLine($") else (");
batchContent.AppendLine($" echo 失败: 无法创建 {fileName}");
batchContent.AppendLine($")");
batchContent.AppendLine($":file_{fileIndex}_end");
fileIndex++;
} }
// 清理工作
batchContent.AppendLine("echo 清理临时文件...");
// 删除锁文件
batchContent.AppendLine($"if exist \"{_updateLockFilePath}\" del \"{_updateLockFilePath}\" /f /q");
// 简化的清理逻辑
batchContent.AppendLine($"cd /d \"{_baseDirectory}\"");
batchContent.AppendLine("for %%f in (*.new) do del \"%%f\" /f /q");
batchContent.AppendLine("for %%f in (*.bak) do del \"%%f\" /f /q");
batchContent.AppendLine("echo 更新完成");
batchContent.AppendLine("ping 127.0.0.1 -n 1 -w 2000 >nul");
// 删除批处理脚本自身
batchContent.AppendLine("del \"%~f0\" /f /q"); batchContent.AppendLine("del \"%~f0\" /f /q");
batchContent.AppendLine("exit");
File.WriteAllText(batchFilePath, batchContent.ToString(), new UTF8Encoding(false)); File.WriteAllText(batchFilePath, batchContent.ToString(), new UTF8Encoding(false));
// 使用Win7兼容的启动方式
var startInfo = new ProcessStartInfo var startInfo = new ProcessStartInfo
{ {
FileName = "cmd.exe", FileName = batchFilePath,
Arguments = $"/c \"{batchFilePath}\"", CreateNoWindow = false, // Win7下设为false更稳定
CreateNoWindow = true, UseShellExecute = true, // Win7下必须使用Shell执行
UseShellExecute = false, WindowStyle = ProcessWindowStyle.Minimized,
WindowStyle = ProcessWindowStyle.Hidden WorkingDirectory = _baseDirectory
}; };
Process.Start(startInfo);
try
{
Process.Start(startInfo);
}
catch (Exception)
{
// 如果上面的方式失败尝试直接用cmd执行
var fallbackStartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c \"{batchFilePath}\"",
CreateNoWindow = true,
UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = _baseDirectory
};
Process.Start(fallbackStartInfo);
}
UpdateStatus("已创建更新脚本,程序退出后将自动完成更新");
} }
catch (Exception ex) catch (Exception ex)
{ {
UpdateStatus($"创建替换脚本时出错: {ex.Message}"); HandleError("创建文件替换脚本 (CreateReplaceScriptForAll)", ex);
} }
} }
@@ -1084,11 +1193,14 @@ namespace CheckDownload
{ {
try try
{ {
_tempDirectory = Path.Combine( string exeDir = AppDomain.CurrentDomain.BaseDirectory;
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Temp", _tempDirectory = Path.Combine(exeDir, "Temp");
"CheckDownload"
); if (File.Exists(_tempDirectory))
{
throw new IOException($"无法创建目录,因为已存在同名文件: {_tempDirectory}");
}
if (!Directory.Exists(_tempDirectory)) if (!Directory.Exists(_tempDirectory))
{ {
@@ -1097,7 +1209,7 @@ namespace CheckDownload
} }
catch (Exception ex) catch (Exception ex)
{ {
UpdateStatus($"初始化临时目录失败: {ex.Message}"); throw new Exception("初始化临时目录 (InitializeTempDirectory)", ex);
} }
} }
@@ -1116,20 +1228,6 @@ namespace CheckDownload
} }
} }
/// <summary>
/// 计算字节数组的MD5哈希值返回32位小写十六进制字符串
/// </summary>
/// <param name="data">要计算哈希值的字节数组</param>
/// <returns>32位小写十六进制MD5哈希值</returns>
private string CalculateMD5(byte[] data)
{
using (var md5 = MD5.Create())
{
byte[] hashBytes = md5.ComputeHash(data);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
}
}
/// <summary> /// <summary>
/// 从阿里云OSS下载文件包含SDK直连和IP直连两种备用方案 /// 从阿里云OSS下载文件包含SDK直连和IP直连两种备用方案
/// </summary> /// </summary>
@@ -1354,7 +1452,7 @@ namespace CheckDownload
string tempPath = Path.Combine( string tempPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Temp", "Temp",
"CheckDownload" _appName
); );
if (Directory.Exists(tempPath)) if (Directory.Exists(tempPath))
@@ -1591,12 +1689,6 @@ namespace CheckDownload
await Task.Run(() => { await Task.Run(() => {
try try
{ {
string sevenZipDllPath = Extract7zDll();
if (string.IsNullOrEmpty(sevenZipDllPath))
{
throw new Exception("无法提取7z.dll解压中止。");
}
string extractionPath = Path.GetDirectoryName(sevenZipFile); string extractionPath = Path.GetDirectoryName(sevenZipFile);
string tempExtractionDir = Path.Combine(_tempDirectory, Path.GetFileNameWithoutExtension(sevenZipFile) + "_temp"); string tempExtractionDir = Path.Combine(_tempDirectory, Path.GetFileNameWithoutExtension(sevenZipFile) + "_temp");
@@ -1606,7 +1698,8 @@ namespace CheckDownload
} }
Directory.CreateDirectory(tempExtractionDir); Directory.CreateDirectory(tempExtractionDir);
using (var archiveFile = new ArchiveFile(sevenZipFile, sevenZipDllPath)) // 使用 SevenZipExtractor 解压文件到临时目录
using (var archiveFile = new ArchiveFile(sevenZipFile, _sevenZipDllPath))
{ {
archiveFile.Extract(tempExtractionDir, true); archiveFile.Extract(tempExtractionDir, true);
} }
@@ -1630,17 +1723,10 @@ namespace CheckDownload
} }
Directory.Delete(tempExtractionDir, true); Directory.Delete(tempExtractionDir, true);
UpdateStatus("为解压的程序设置管理员权限...");
var exeFiles = Directory.GetFiles(extractionPath, "*.exe", SearchOption.AllDirectories);
foreach (var exeFile in exeFiles)
{
SetRunAsAdminCompatibility(exeFile);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception($"解压失败: {ex.Message}"); throw new Exception("DecompressTim7zAsync: 解压失败。", ex);
} }
}); });
@@ -1650,8 +1736,7 @@ namespace CheckDownload
} }
catch (Exception ex) catch (Exception ex)
{ {
UpdateStatus($"处理 tim.7z 时出错: {ex.Message}"); throw new Exception("DecompressTim7zAsync: 处理 tim.7z 时出错。", ex);
await Task.Delay(3000);
} }
} }
@@ -1659,69 +1744,6 @@ namespace CheckDownload
/// 为指定程序路径在注册表中设置"以管理员身份运行"的兼容性标志。 /// 为指定程序路径在注册表中设置"以管理员身份运行"的兼容性标志。
/// </summary> /// </summary>
/// <param name="exePath">要设置的.exe文件的完整路径。</param> /// <param name="exePath">要设置的.exe文件的完整路径。</param>
private void SetRunAsAdminCompatibility(string exePath)
{
const string keyPath = @"Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers";
try
{
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(keyPath))
{
if (key != null)
{
key.SetValue(exePath, "~ RUNASADMIN");
}
else
{
UpdateStatus($"无法打开或创建注册表项: {keyPath}");
}
}
}
catch (Exception ex)
{
UpdateStatus($"设置管理员权限失败: {exePath} - {ex.Message}");
}
}
/// <summary>
/// 从嵌入的资源中提取与当前进程体系结构匹配的7z.dll到临时目录。
/// </summary>
/// <returns>提取的7z.dll的路径如果失败则返回null。</returns>
private string Extract7zDll()
{
try
{
string dllName = Environment.Is64BitProcess ? "7z-x64.dll" : "7z-x86.dll";
string resourceName = $"CheckDownload.{dllName}";
string dllPath = Path.Combine(_tempDirectory, "7z.dll");
if (File.Exists(dllPath))
{
// 可以选择在这里添加对现有DLL版本的校验但为简化我们先直接返回
return dllPath;
}
using (var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
if (resourceStream == null)
{
UpdateStatus($"在嵌入资源中未找到 {dllName}。");
return null;
}
using (var fileStream = new FileStream(dllPath, FileMode.Create, FileAccess.Write))
{
resourceStream.CopyTo(fileStream);
}
}
return dllPath;
}
catch (Exception ex)
{
UpdateStatus($"提取7z.dll失败: {ex.Message}");
return null;
}
}
// add helper to check db extension // add helper to check db extension
private bool IsDatabaseFile(string relativePath) private bool IsDatabaseFile(string relativePath)
{ {
@@ -1729,17 +1751,34 @@ namespace CheckDownload
return ext == ".db" || ext == ".db3"; return ext == ".db" || ext == ".db3";
} }
private void KillProcessIfRunning(string exeRelativePath) private void KillProcessIfRunning(string exePath)
{ {
try try
{ {
string exeName = Path.GetFileNameWithoutExtension(exeRelativePath); string exeName = Path.GetFileNameWithoutExtension(exePath);
string targetExeDir;
// 判断是否为完整路径
if (Path.IsPathRooted(exePath))
{
targetExeDir = Path.GetDirectoryName(exePath);
}
else
{
targetExeDir = Path.GetDirectoryName(Path.Combine(_baseDirectory, exePath));
}
foreach (var proc in Process.GetProcessesByName(exeName)) foreach (var proc in Process.GetProcessesByName(exeName))
{ {
if (proc.Id == _currentProcessId) continue;
try try
{ {
proc.Kill(); string runningProcessDir = Path.GetDirectoryName(proc.MainModule.FileName);
proc.WaitForExit(5000); if (string.Equals(runningProcessDir, targetExeDir, StringComparison.OrdinalIgnoreCase))
{
proc.Kill();
proc.WaitForExit(5000);
}
} }
catch { } catch { }
} }
@@ -1751,22 +1790,35 @@ namespace CheckDownload
{ {
try try
{ {
foreach (var proc in Process.GetProcesses()) foreach (var proc in Process.GetProcessesByName(baseName))
{ {
if (string.Equals(proc.ProcessName, baseName, StringComparison.OrdinalIgnoreCase)) if (proc.Id == _currentProcessId) continue;
try
{ {
try if (proc.MainModule.FileName.StartsWith(_baseDirectory, StringComparison.OrdinalIgnoreCase))
{ {
proc.Kill(); proc.Kill();
proc.WaitForExit(5000); proc.WaitForExit(5000);
} }
catch { }
} }
catch { }
} }
} }
catch { } catch { }
} }
/// <summary>
/// Truncates a string to a maximum length and appends an ellipsis.
/// </summary>
/// <param name="value">The string to truncate.</param>
/// <param name="maxLength">The maximum length of the string.</param>
/// <returns>The truncated string.</returns>
private string TruncateString(string value, int maxLength)
{
if (string.IsNullOrEmpty(value)) return value;
return value.Length <= maxLength ? value : value.Substring(0, maxLength) + "...";
}
// === 新增: 更新整体下载大小与速度 === // === 新增: 更新整体下载大小与速度 ===
private void UpdateOverallSize() private void UpdateOverallSize()
{ {
@@ -1787,5 +1839,124 @@ namespace CheckDownload
_lastSpeedUpdateTime = now; _lastSpeedUpdateTime = now;
UpdateSize(sizeText); UpdateSize(sizeText);
} }
/// <summary>
/// 统一处理并显示错误信息
/// </summary>
/// <param name="location">要显示给用户的基本错误信息</param>
/// <param name="ex">可选的异常对象,其消息将被附加</param>
private void HandleError(string location, Exception ex = null)
{
UpdateStatus("更新失败");
// 递归查找最深层的 InnerException 以获取最根本的错误信息
var innermostException = ex;
while (innermostException?.InnerException != null)
{
innermostException = innermostException.InnerException;
}
string errorMessage = innermostException?.Message ?? ex?.Message ?? "发生未知错误。";
MessageBox.Show(errorMessage, "更新错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
/// <summary>
/// 尝试将文件从源位置复制到目标位置
/// </summary>
private async Task<bool> TryCopyFileAsync(string sourcePath, string targetPath)
{
try
{
// 如果目标文件是.exe文件先检查并kill掉可能正在运行的进程
if (Path.GetExtension(targetPath).Equals(".exe", StringComparison.OrdinalIgnoreCase))
{
KillProcessIfRunning(targetPath);
}
// 确保目标目录存在
string targetDir = Path.GetDirectoryName(targetPath);
if (!Directory.Exists(targetDir))
{
Directory.CreateDirectory(targetDir);
}
// 执行复制操作
File.Copy(sourcePath, targetPath, true);
return true;
}
catch (Exception ex)
{
// 文件可能被占用,稍后重试
try
{
await Task.Delay(1000);
File.Copy(sourcePath, targetPath, true);
return true;
}
catch
{
UpdateStatus($"文件复制失败: {Path.GetFileName(targetPath)}");
return false;
}
}
}
/// <summary>
/// 创建临时文件的备份,防止在程序退出后被删除
/// </summary>
private async Task CreateTempFileBackup()
{
try
{
string tempPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Temp",
_appName
);
if (Directory.Exists(tempPath))
{
string backupDir = Path.Combine(_baseDirectory, "TempBackup");
if (!Directory.Exists(backupDir))
{
Directory.CreateDirectory(backupDir);
}
string timestamp = DateTime.Now.ToString("yyyyMMddHHmmss");
string backupPath = Path.Combine(backupDir, $"Temp_{timestamp}");
// 简单复制整个临时目录
await Task.Run(() => CopyDirectory(tempPath, backupPath));
//UpdateStatus($"[调试] 临时文件已备份到: {backupPath}");
}
}
catch (Exception ex)
{
UpdateStatus($"创建临时文件备份失败: {ex.Message}");
}
}
/// <summary>
/// 递归复制目录
/// </summary>
private void CopyDirectory(string sourceDir, string targetDir)
{
Directory.CreateDirectory(targetDir);
foreach (var file in Directory.GetFiles(sourceDir))
{
string fileName = Path.GetFileName(file);
string targetFile = Path.Combine(targetDir, fileName);
File.Copy(file, targetFile, true);
}
foreach (var subDir in Directory.GetDirectories(sourceDir))
{
string dirName = Path.GetFileName(subDir);
string targetSubDir = Path.Combine(targetDir, dirName);
CopyDirectory(subDir, targetSubDir);
}
}
} }
} }