Compare commits
14 Commits
CountSIze
...
218e1456f2
Author | SHA1 | Date | |
---|---|---|---|
218e1456f2 | |||
734d3bb348 | |||
b294c96687 | |||
9084f35b8f | |||
6d12a89c9c | |||
848de6f3fa | |||
9b790fe9e0 | |||
f950b05b62 | |||
a3bfee2755 | |||
![]() |
e3b32ed453 | ||
![]() |
0891d7534a | ||
ef9aa7bc5e | |||
9ba1b33ca3 | |||
96cfa4be48 |
@@ -137,16 +137,16 @@
|
||||
<Reference Include="System.Configuration" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Form1.cs">
|
||||
<Compile Include="Update.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Form1.Designer.cs">
|
||||
<DependentUpon>Form1.cs</DependentUpon>
|
||||
<Compile Include="Update.Designer.cs">
|
||||
<DependentUpon>Update.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<EmbeddedResource Include="Form1.resx">
|
||||
<DependentUpon>Form1.cs</DependentUpon>
|
||||
<EmbeddedResource Include="Update.resx">
|
||||
<DependentUpon>Update.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
@@ -184,22 +184,23 @@
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="7z-x86.dll" />
|
||||
<EmbeddedResource Include="7z-x64.dll" />
|
||||
<Content Include="7z.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用"NuGet 程序包还原"可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
|
||||
</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.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\SevenZipExtractor.1.0.19\build\SevenZipExtractor.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\SevenZipExtractor.1.0.19\build\SevenZipExtractor.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'))" /> -->
|
||||
</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\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')" />
|
||||
<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\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>
|
130
README.md
130
README.md
@@ -32,8 +32,8 @@
|
||||
|
||||
## 用户界面
|
||||
|
||||
- **实时进度显示**:通过进度条、已完成数量/总数、下载速度等信息,清晰地展示更新进度。
|
||||
- **简洁的状态反馈**:界面只显示当前正在处理的文件名等核心信息,避免被冗长的日志刷屏。
|
||||
- **实时进度显示**:通过进度条、已完成数量/总数以及 **已下载总量(实时速度)** 的形式清晰地展示更新进度。已下载总量会包含已存在于临时目录的有效文件,速度则只计算运行时下载的部分。
|
||||
- **简洁的状态反馈**:界面会以 `下载:文件名...` 的格式显示当前任务,过长的文件名会被自动截断,避免信息刷屏。
|
||||
- **友好的错误提示**:当发生严重错误时,会弹出简明扼要的错误信息窗口,而不是难以理解的完整堆栈跟踪。
|
||||
- **自动定位与退出**:窗体启动时会自动停靠在屏幕右下角,更新完成后会自动关闭,对用户干扰极小。
|
||||
|
||||
@@ -53,45 +53,43 @@
|
||||
## 🚀 主要功能
|
||||
|
||||
### 📥 多源下载支持
|
||||
- **123盘云存储**: 支持主备域名自动切换,提供高可用性下载服务
|
||||
- **阿里云OSS**: 备用下载源,确保文件下载的可靠性
|
||||
- **智能DNS解析**: 使用多个DNS服务器提高域名解析成功率
|
||||
- **123盘云存储**: (UID: `1826795402`, Path: `/1826795402/KeyAuth`) 支持主备域名自动切换,提供高可用性下载服务。
|
||||
- **阿里云OSS**: 备用下载源,确保文件下载的可靠性。
|
||||
- **智能DNS解析**: 使用多个DNS服务器提高域名解析成功率。
|
||||
|
||||
### 🔄 智能更新机制
|
||||
- **MD5完整性验证**: 下载前后进行MD5校验,确保文件完整性
|
||||
- **增量更新**: 仅下载已变更的文件,节省带宽和时间
|
||||
- **断点续传**: 支持网络中断后继续下载,提高下载成功率
|
||||
- **并发下载**: 可配置的多线程并发下载,显著提升下载速度
|
||||
- **MD5完整性验证**: 下载前后进行MD5校验,确保文件完整性。
|
||||
- **增量更新**: 仅下载已变更的文件,节省带宽和时间。
|
||||
- **断点续传支持**: 通过检查本地临时文件实现,若文件已存在且MD5匹配,则跳过下载。
|
||||
- **并发下载**: 可配置的多线程并发下载,显著提升下载速度。
|
||||
|
||||
### 🛡️ 文件处理与安全
|
||||
- **文件占用处理**: 智能检测并处理被占用的文件
|
||||
- **批处理脚本**: 为被占用文件创建延迟替换脚本
|
||||
- **临时文件管理**: 自动清理临时文件,保持系统整洁
|
||||
- **路径智能识别**: 基于MD5数据自动识别项目基准目录
|
||||
- **文件占用处理**: 智能检测并处理被占用的文件,并创建延迟替换脚本。
|
||||
- **临时文件管理**: 自动清理临时文件,保持系统整洁。
|
||||
- **路径智能识别**: 基于MD5数据自动识别项目基准目录。
|
||||
|
||||
### 📦 自动解压功能
|
||||
- **7z格式支持**: 内置7z解压引擎,支持多种压缩格式
|
||||
- **自动权限设置**: 解压后自动为exe文件设置管理员运行权限
|
||||
- **覆盖解压**: 支持覆盖模式解压,确保文件更新
|
||||
- **多架构兼容**: 自动选择32位/64位解压库,适配不同运行环境
|
||||
- **7z格式支持**: 内置7z解压引擎,支持多种压缩格式。
|
||||
- **自动权限设置**: 解压后自动为exe文件设置管理员运行权限。
|
||||
- **多架构兼容**: 自动选择32位/64位解压库,适配不同运行环境。
|
||||
|
||||
### 🔧 错误处理与重试
|
||||
- **多次重试机制**: 下载失败自动重试,可配置重试次数
|
||||
- **异常处理**: 完善的异常捕获和处理机制
|
||||
- **状态实时显示**: 实时显示下载进度、状态和错误信息
|
||||
- **多次重试机制**: 下载失败自动重试,可配置重试次数。
|
||||
- **异常处理**: 完善的异常捕获和处理机制。
|
||||
- **状态实时显示**: 实时显示下载进度、状态和错误信息。
|
||||
|
||||
## 🏗️ 技术架构
|
||||
|
||||
### 核心组件
|
||||
- **.NET Framework 4.7.2**: 基于稳定的.NET Framework构建
|
||||
- **异步编程**: 全面采用async/await模式,确保UI响应性
|
||||
- **多线程下载**: 使用SemaphoreSlim控制并发数量
|
||||
- **资源嵌入**: 将依赖库嵌入程序,实现单文件部署
|
||||
- **.NET Framework**: 基于稳定的.NET Framework构建。
|
||||
- **异步编程**: 全面采用async/await模式,确保UI响应性。
|
||||
- **多线程下载**: 使用SemaphoreSlim控制并发数量。
|
||||
- **资源嵌入**: 将依赖库嵌入程序,实现单文件部署。
|
||||
|
||||
### 依赖库
|
||||
- **Aliyun.OSS.SDK**: 阿里云对象存储服务支持
|
||||
- **Newtonsoft.Json**: JSON数据处理
|
||||
- **SevenZipExtractor**: 7z压缩文件解压支持
|
||||
- **Aliyun.OSS.SDK**: 阿里云对象存储服务支持。
|
||||
- **Newtonsoft.Json**: JSON数据处理。
|
||||
- **SevenZipExtractor**: 7z压缩文件解压支持。
|
||||
|
||||
## 📋 配置说明
|
||||
|
||||
@@ -115,79 +113,29 @@
|
||||
"version": "1.0.0",
|
||||
"data": {
|
||||
"program.exe": "5d41402abc4b2a76b9719d911017c592",
|
||||
"lib/library.dll": "098f6bcd4621d373cade4e832627b4f6",
|
||||
"config/settings.ini": "5e40d4c123456789abcdef1234567890"
|
||||
"lib/library.dll": "098f6bcd4621d373cade4e832627b4f6"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔄 工作流程
|
||||
|
||||
1. **启动检查**: 程序启动时清理旧的临时文件
|
||||
2. **下载MD5**: 从云存储下载最新的MD5文件
|
||||
3. **文件比较**: 对比本地文件与在线MD5,识别需要更新的文件
|
||||
4. **并发下载**: 使用多线程下载需要更新的文件
|
||||
5. **完整性验证**: 验证下载文件的MD5值
|
||||
6. **文件替换**: 将新文件移动到目标位置
|
||||
7. **7z解压**: 自动检测并解压tim.7z文件
|
||||
8. **权限设置**: 为解压的exe文件设置管理员运行权限
|
||||
9. **清理完成**: 清理临时文件,显示完成状态
|
||||
1. **启动检查**: 程序启动时清理旧的更新文件 (`.new` 后缀)。
|
||||
2. **下载MD5**: 从云存储下载最新的MD5文件。
|
||||
3. **文件比较**: 对比本地文件与在线MD5,识别需要更新的文件。
|
||||
4. **并发下载**: 使用多线程下载需要更新的文件到临时目录。
|
||||
5. **完整性验证**: 验证下载文件的MD5值。
|
||||
6. **文件替换**: 将新文件移动到目标位置,对被占用文件创建替换脚本。
|
||||
7. **7z解压**: 自动检测并解压`tim.7z`文件(如果存在)。
|
||||
8. **权限设置**: 为解压的exe文件设置管理员运行权限。
|
||||
9. **清理完成**: 清理临时目录,显示完成状态后退出。
|
||||
|
||||
## 🎯 使用场景
|
||||
|
||||
- **软件自动更新**: 为桌面应用程序提供自动更新功能
|
||||
- **文件同步**: 在不同设备间同步文件和配置
|
||||
- **批量部署**: 企业环境下的软件批量部署和更新
|
||||
- **游戏更新**: 游戏客户端的增量更新和补丁分发
|
||||
|
||||
## 🛠️ 开发特性
|
||||
|
||||
### 网络优化
|
||||
- **静态HttpClient**: 避免套接字耗尽问题
|
||||
- **连接池复用**: 提高网络请求效率
|
||||
- **超时控制**: 合理的超时设置,避免长时间等待
|
||||
|
||||
### 内存管理
|
||||
- **流式处理**: 大文件下载使用流式处理,控制内存占用
|
||||
- **及时释放**: 及时释放不再使用的资源
|
||||
- **临时文件**: 合理使用临时文件,避免内存溢出
|
||||
|
||||
### 用户体验
|
||||
- **进度显示**: 实时显示下载进度和文件信息
|
||||
- **状态反馈**: 详细的状态信息和错误提示
|
||||
- **窗口定位**: 智能定位到屏幕右下角,不影响用户操作
|
||||
|
||||
## 📈 性能特点
|
||||
|
||||
- **高效下载**: 多线程并发下载,充分利用网络带宽
|
||||
- **智能重试**: 失败文件智能重试,提高成功率
|
||||
- **资源节约**: 仅下载变更文件,节省网络流量
|
||||
- **快速启动**: 优化的启动流程,快速进入工作状态
|
||||
|
||||
## 🔒 安全性
|
||||
|
||||
- **MD5验证**: 确保文件在传输过程中没有被篡改
|
||||
- **用户权限**: 在当前用户权限下操作,避免权限滥用
|
||||
- **临时目录**: 使用用户临时目录,避免权限问题
|
||||
- **异常处理**: 完善的异常处理,避免程序崩溃
|
||||
|
||||
## 📝 版本历史
|
||||
|
||||
### 最新版本特性
|
||||
- ✅ 添加7z自动解压功能
|
||||
- ✅ 支持解压后程序自动设置管理员权限
|
||||
- ✅ 优化多线程下载性能
|
||||
- ✅ 增强错误处理和重试机制
|
||||
- ✅ 改进用户界面和状态显示
|
||||
- ✅ 过滤 .db / .db3 数据库文件,避免占用导致失败
|
||||
- ✅ 更新列表或压缩包中出现 tim.dll 时,自动结束 tim.exe 后替换
|
||||
- ✅ 所有文件匹配与进程检测均采用大小写不敏感逻辑,避免大小写差异造成的更新失败
|
||||
- ✅ 精简 Status_Box 文本,并在失败时弹窗展示核心错误信息
|
||||
|
||||
## 🤝 技术支持
|
||||
|
||||
如需技术支持或报告问题,请联系开发团队。
|
||||
- **软件自动更新**: 为桌面应用程序提供自动更新功能。
|
||||
- **文件同步**: 在不同设备间同步文件和配置。
|
||||
- **游戏更新**: 游戏客户端的增量更新和补丁分发。
|
||||
|
||||
---
|
||||
|
||||
**注意**: 本程序需要网络连接以下载更新文件。首次运行时可能需要较长时间来下载必要的文件。
|
||||
**注意**: 本程序需要网络连接以下载更新文件。首次运行时可能需要较长时间来下载所有必要的文件。
|
19
Form1.Designer.cs → Update.Designer.cs
generated
19
Form1.Designer.cs → Update.Designer.cs
generated
@@ -28,22 +28,12 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.Update_Text = new System.Windows.Forms.Label();
|
||||
this.Update_Pro = new System.Windows.Forms.ProgressBar();
|
||||
this.Status_Box = new System.Windows.Forms.Label();
|
||||
this.Count_Box = new System.Windows.Forms.Label();
|
||||
this.Size_Box = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// Update_Text
|
||||
//
|
||||
this.Update_Text.AutoSize = true;
|
||||
this.Update_Text.Location = new System.Drawing.Point(12, 12);
|
||||
this.Update_Text.Name = "Update_Text";
|
||||
this.Update_Text.Size = new System.Drawing.Size(59, 12);
|
||||
this.Update_Text.TabIndex = 0;
|
||||
this.Update_Text.Text = "更新状态:";
|
||||
//
|
||||
// Update_Pro
|
||||
//
|
||||
this.Update_Pro.Location = new System.Drawing.Point(12, 36);
|
||||
@@ -53,10 +43,9 @@
|
||||
//
|
||||
// Status_Box
|
||||
//
|
||||
this.Status_Box.AutoSize = false;
|
||||
this.Status_Box.Location = new System.Drawing.Point(72, 12);
|
||||
this.Status_Box.Location = new System.Drawing.Point(12, 12);
|
||||
this.Status_Box.Name = "Status_Box";
|
||||
this.Status_Box.Size = new System.Drawing.Size(94, 12);
|
||||
this.Status_Box.Size = new System.Drawing.Size(148, 12);
|
||||
this.Status_Box.TabIndex = 2;
|
||||
this.Status_Box.Text = "...";
|
||||
//
|
||||
@@ -86,7 +75,6 @@
|
||||
this.Controls.Add(this.Count_Box);
|
||||
this.Controls.Add(this.Status_Box);
|
||||
this.Controls.Add(this.Update_Pro);
|
||||
this.Controls.Add(this.Update_Text);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
@@ -96,13 +84,10 @@
|
||||
this.TopMost = true;
|
||||
this.Load += new System.EventHandler(this.Update_Load);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label Update_Text;
|
||||
private System.Windows.Forms.ProgressBar Update_Pro;
|
||||
private System.Windows.Forms.Label Status_Box;
|
||||
private System.Windows.Forms.Label Count_Box;
|
@@ -75,15 +75,40 @@ namespace CheckDownload
|
||||
// 基准目录路径(用于文件更新的目标目录)
|
||||
private string _baseDirectory;
|
||||
|
||||
// 7z.dll 库文件路径
|
||||
private readonly string _sevenZipDllPath;
|
||||
|
||||
// 应用程序名称
|
||||
private readonly string _appName;
|
||||
|
||||
// 当前进程ID
|
||||
private readonly int _currentProcessId;
|
||||
|
||||
// === 新增: 用于显示整体下载大小与速度 ===
|
||||
private long _totalDownloadedBytes = 0; // 已下载总字节数
|
||||
private DateTime _downloadStartTime; // 下载开始时间
|
||||
private DateTime _lastSpeedUpdateTime; // 上一次更新速度的时间
|
||||
private readonly object _speedLock = new object(); // 锁,用于多线程更新 UI
|
||||
private long _bytesSinceLastSpeedCalc = 0; // 距离上次速度计算新增的字节数
|
||||
|
||||
// 更新锁文件路径
|
||||
private string _updateLockFilePath;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化窗体
|
||||
/// </summary>
|
||||
public Update()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 设置 7z.dll 的路径为程序运行目录
|
||||
_sevenZipDllPath = Path.Combine(Application.StartupPath, "7z.dll");
|
||||
|
||||
_appName = Assembly.GetExecutingAssembly().GetName().Name;
|
||||
_currentProcessId = Process.GetCurrentProcess().Id;
|
||||
_baseDirectory = Application.StartupPath;
|
||||
_updateLockFilePath = Path.Combine(_baseDirectory, "update.lock");
|
||||
ConfigureProgressBar();
|
||||
InitializeTempDirectory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -127,21 +152,13 @@ namespace CheckDownload
|
||||
/// <param name="message">状态消息</param>
|
||||
private void UpdateStatus(string message)
|
||||
{
|
||||
// 只保留冒号后的简短信息(一般是文件名);若无冒号则原样显示
|
||||
string display = message;
|
||||
int idx = message.LastIndexOf(':');
|
||||
if (idx >= 0 && idx < message.Length - 1)
|
||||
{
|
||||
display = message.Substring(idx + 1).Trim();
|
||||
}
|
||||
|
||||
if (this.InvokeRequired)
|
||||
{
|
||||
this.Invoke((MethodInvoker)delegate { Status_Box.Text = display; });
|
||||
this.Invoke((MethodInvoker)delegate { Status_Box.Text = message; });
|
||||
}
|
||||
else
|
||||
{
|
||||
Status_Box.Text = display;
|
||||
Status_Box.Text = message;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +206,26 @@ namespace CheckDownload
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 删除更新锁文件,让批处理脚本知道程序已退出
|
||||
CleanupLockFile();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理更新锁文件
|
||||
/// </summary>
|
||||
private void CleanupLockFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_updateLockFilePath) && File.Exists(_updateLockFilePath))
|
||||
{
|
||||
File.Delete(_updateLockFilePath);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略清理锁文件时的错误
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +245,7 @@ namespace CheckDownload
|
||||
{
|
||||
try
|
||||
{
|
||||
InitializeTempDirectory();
|
||||
CleanupNewFiles();
|
||||
UpdateStatus("下载在线MD5文件并读取...");
|
||||
UpdateCount("");
|
||||
@@ -247,7 +285,7 @@ namespace CheckDownload
|
||||
UpdateProgressValue(100);
|
||||
|
||||
// 无需更新时清理临时文件夹
|
||||
CleanupTempDirectory();
|
||||
//CleanupTempDirectory();
|
||||
|
||||
// 显示更新完成并等待2秒
|
||||
UpdateStatus("更新完成");
|
||||
@@ -270,6 +308,13 @@ namespace CheckDownload
|
||||
_totalCount = orderedFileList.Count;
|
||||
_completedCount = 0;
|
||||
_downloadedFiles.Clear();
|
||||
|
||||
// === 新增: 初始化速度统计 ===
|
||||
_totalDownloadedBytes = 0;
|
||||
_downloadStartTime = DateTime.UtcNow;
|
||||
_lastSpeedUpdateTime = _downloadStartTime;
|
||||
_bytesSinceLastSpeedCalc = 0;
|
||||
|
||||
var failedFiles = new ConcurrentDictionary<string, string>();
|
||||
|
||||
await PerformDownloads(orderedFileList, failedFiles);
|
||||
@@ -290,7 +335,7 @@ namespace CheckDownload
|
||||
|
||||
if (_completedCount == 0 && orderedFileList.Count > 0)
|
||||
{
|
||||
throw new Exception("所有文件下载失败。");
|
||||
throw new Exception("UpdateFile: 所有文件下载失败。");
|
||||
}
|
||||
|
||||
await VerifyAndSaveAllFiles();
|
||||
@@ -298,7 +343,7 @@ namespace CheckDownload
|
||||
await DecompressTim7zAsync();
|
||||
|
||||
// 校验和保存成功后清理临时目录
|
||||
CleanupTempDirectory();
|
||||
//CleanupTempDirectory();
|
||||
|
||||
// 显示完成状态并退出
|
||||
UpdateStatus("更新完成");
|
||||
@@ -308,8 +353,7 @@ namespace CheckDownload
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UpdateStatus("更新失败");
|
||||
MessageBox.Show($"更新失败:\n{ex.ToString()}", "Update Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
HandleError("文件更新主流程 (UpdateFile)", ex);
|
||||
await Task.Delay(3000);
|
||||
this.Close();
|
||||
}
|
||||
@@ -364,8 +408,7 @@ namespace CheckDownload
|
||||
}
|
||||
catch (Exception ex) when (ex is OssException || ex is JsonException)
|
||||
{
|
||||
UpdateStatus($"读取在线MD5文件失败: {ex.Message}");
|
||||
return (null, null, null);
|
||||
throw new Exception($"ReadOnlineMd5File: 读取或解析在线MD5文件 '{filePath}' 失败。", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,37 +490,6 @@ namespace CheckDownload
|
||||
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>
|
||||
@@ -503,7 +515,12 @@ namespace CheckDownload
|
||||
int progress = (int)((double)_completedCount / _totalCount * 95);
|
||||
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}");
|
||||
}
|
||||
else
|
||||
@@ -527,14 +544,9 @@ namespace CheckDownload
|
||||
/// <returns>下载成功返回true,否则返回false</returns>
|
||||
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 fileName = Path.GetFileName(filePath);
|
||||
string truncatedFileName = TruncateString(fileName, 10);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -543,14 +555,20 @@ namespace CheckDownload
|
||||
return true;
|
||||
}
|
||||
|
||||
UpdateStatus($"{fileName}");
|
||||
if (!string.IsNullOrWhiteSpace(truncatedFileName))
|
||||
{
|
||||
UpdateStatus($"下载:{truncatedFileName}");
|
||||
}
|
||||
UpdateCount($"{_completedCount + 1}/{_totalCount}");
|
||||
if (await DownloadFileFromOneDrive(filePath, expectedMd5, tempFilePath))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
UpdateStatus($"{fileName}");
|
||||
if (!string.IsNullOrWhiteSpace(truncatedFileName))
|
||||
{
|
||||
UpdateStatus($"下载:{truncatedFileName}");
|
||||
}
|
||||
UpdateCount($"{_completedCount + 1}/{_totalCount}");
|
||||
string ossKey = $"File/{expectedMd5}";
|
||||
|
||||
@@ -570,7 +588,10 @@ namespace CheckDownload
|
||||
}
|
||||
catch (Exception ex) when (ex is OssException || ex is WebException)
|
||||
{
|
||||
UpdateStatus($"{fileName}");
|
||||
if (!string.IsNullOrWhiteSpace(truncatedFileName))
|
||||
{
|
||||
UpdateStatus($"下载:{truncatedFileName}");
|
||||
}
|
||||
UpdateCount($"{_completedCount + 1}/{_totalCount}");
|
||||
UpdateSize("");
|
||||
string ossKey = $"File/{expectedMd5}";
|
||||
@@ -578,7 +599,10 @@ namespace CheckDownload
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UpdateStatus($"下载异常: {fileName} - {ex.Message}");
|
||||
if (!string.IsNullOrWhiteSpace(truncatedFileName))
|
||||
{
|
||||
UpdateStatus($"下载异常: {truncatedFileName} - {ex.Message}");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -596,28 +620,48 @@ namespace CheckDownload
|
||||
{
|
||||
if (!File.Exists(tempFilePath))
|
||||
{
|
||||
//UpdateStatus($"[调试] 临时文件不存在: {fileName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateStatus($"检查已存在的临时文件: {fileName}");
|
||||
if (!string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
UpdateStatus($"检查已存在的临时文件: {fileName}");
|
||||
}
|
||||
|
||||
string actualMd5 = await Task.Run(() => CalculateMD5FromFile(tempFilePath));
|
||||
|
||||
if (actualMd5.Equals(expectedMd5, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
UpdateStatus($"临时文件完整,跳过下载: {fileName}");
|
||||
if (!string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
UpdateStatus($"临时文件完整,跳过下载: {fileName}");
|
||||
}
|
||||
// === 新增: 将已存在的有效临时文件大小计入总下载量 ===
|
||||
try
|
||||
{
|
||||
var fileInfo = new FileInfo(tempFilePath);
|
||||
Interlocked.Add(ref _totalDownloadedBytes, fileInfo.Length);
|
||||
//UpdateStatus($"[调试] 临时文件验证通过,大小: {fileInfo.Length} bytes - {fileName}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略获取文件大小的错误
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateStatus($"临时文件不完整,重新下载: {fileName}");
|
||||
File.Delete(tempFilePath);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UpdateStatus($"检查临时文件时出错,将重新下载: {fileName} - {ex.Message}");
|
||||
if (!string.IsNullOrWhiteSpace(fileName))
|
||||
{
|
||||
UpdateStatus($"临时文件异常,删除文件: {fileName} - {ex.Message}");
|
||||
}
|
||||
try
|
||||
{
|
||||
if (File.Exists(tempFilePath))
|
||||
@@ -654,7 +698,6 @@ namespace CheckDownload
|
||||
UpdateSize("");
|
||||
|
||||
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))
|
||||
{
|
||||
@@ -697,7 +740,6 @@ namespace CheckDownload
|
||||
UpdateSize("");
|
||||
|
||||
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))
|
||||
{
|
||||
@@ -743,7 +785,6 @@ namespace CheckDownload
|
||||
string authUrl = GenerateAuthUrl($"http://{OneDriveMainDomain}{OneDrivePath}", fileName);
|
||||
|
||||
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))
|
||||
{
|
||||
@@ -772,7 +813,6 @@ namespace CheckDownload
|
||||
string authUrl = GenerateAuthUrl($"http://{OneDriveBackupDomain}{OneDrivePath}", fileName);
|
||||
|
||||
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))
|
||||
{
|
||||
@@ -843,7 +883,6 @@ namespace CheckDownload
|
||||
{
|
||||
var requestUri = $"http://{dnsServer}/resolve?name={domain}&type=1&short=1";
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, requestUri) { Version = HttpVersion.Version11 };
|
||||
request.Headers.Add("User-Agent", "CheckDownload/1.0");
|
||||
request.Headers.Host = dnsServer;
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
@@ -876,6 +915,10 @@ namespace CheckDownload
|
||||
UpdateStatus("正在校验文件...");
|
||||
UpdateCount("");
|
||||
UpdateSize("");
|
||||
|
||||
// 新增:在开始校验前创建临时文件备份
|
||||
//await CreateTempFileBackup();
|
||||
|
||||
var failedFiles = new ConcurrentBag<string>();
|
||||
var filesForScripting = new ConcurrentBag<(string original, string newFile)>();
|
||||
|
||||
@@ -892,9 +935,18 @@ namespace CheckDownload
|
||||
string expectedMd5 = item.Value;
|
||||
string tempFilePath = Path.Combine(_tempDirectory, relativePath);
|
||||
|
||||
// 添加调试信息:检查临时文件是否存在
|
||||
if (!File.Exists(tempFilePath))
|
||||
{
|
||||
//UpdateStatus($"[调试] 临时文件在校验前消失: {relativePath}");
|
||||
failedFiles.Add(relativePath);
|
||||
return;
|
||||
}
|
||||
|
||||
string actualMd5 = CalculateMD5FromFile(tempFilePath);
|
||||
if (actualMd5 != expectedMd5.ToLower())
|
||||
{
|
||||
//UpdateStatus($"[调试] 文件MD5校验失败: {relativePath}");
|
||||
failedFiles.Add(relativePath);
|
||||
return;
|
||||
}
|
||||
@@ -906,13 +958,24 @@ namespace CheckDownload
|
||||
Directory.CreateDirectory(localDir);
|
||||
}
|
||||
|
||||
if (!await TryMoveFileAsync(tempFilePath, localPath))
|
||||
//UpdateStatus($"[调试] 开始复制文件: {relativePath}");
|
||||
if (!await TryCopyFileAsync(tempFilePath, localPath)) // 改为使用复制方法
|
||||
{
|
||||
//UpdateStatus($"[调试] 直接复制失败,创建.new文件: {relativePath}");
|
||||
string backupPath = localPath + ".new";
|
||||
File.Move(tempFilePath, backupPath);
|
||||
File.Copy(tempFilePath, backupPath, true); // 复制而非移动
|
||||
filesForScripting.Add((localPath, backupPath));
|
||||
}
|
||||
else
|
||||
{
|
||||
//UpdateStatus($"[调试] 文件复制成功: {relativePath}");
|
||||
}
|
||||
|
||||
// 复制后再次检查临时文件是否还存在
|
||||
if (!File.Exists(tempFilePath))
|
||||
{
|
||||
//UpdateStatus($"[调试] 警告:文件复制后临时文件消失: {relativePath}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -937,7 +1000,9 @@ namespace CheckDownload
|
||||
|
||||
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
|
||||
{
|
||||
@@ -998,67 +1063,154 @@ namespace CheckDownload
|
||||
{
|
||||
string batchFilePath = Path.Combine(_baseDirectory, "update_files.bat");
|
||||
string processId = Process.GetCurrentProcess().Id.ToString();
|
||||
string processName = Process.GetCurrentProcess().ProcessName;
|
||||
|
||||
// 创建锁文件,程序退出前会删除它
|
||||
File.WriteAllText(_updateLockFilePath, processId);
|
||||
|
||||
var batchContent = new StringBuilder();
|
||||
batchContent.AppendLine("@echo off");
|
||||
batchContent.AppendLine("chcp 65001 > nul");
|
||||
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("title Update Process");
|
||||
|
||||
// 简化的等待逻辑,避免复杂的计数器
|
||||
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("timeout /t 2 /nobreak > NUL");
|
||||
|
||||
// 额外等待确保完全退出
|
||||
batchContent.AppendLine("ping 127.0.0.1 -n 1 -w 1000 >nul");
|
||||
batchContent.AppendLine("echo 开始更新文件...");
|
||||
|
||||
// 简化的文件处理逻辑,避免复杂的标签
|
||||
int fileIndex = 0;
|
||||
foreach (var file in files)
|
||||
{
|
||||
batchContent.AppendLine($"del \"{file.original}\" /f /q");
|
||||
batchContent.AppendLine($"move \"{file.newFile}\" \"{file.original}\"");
|
||||
string fileName = Path.GetFileName(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("exit");
|
||||
|
||||
File.WriteAllText(batchFilePath, batchContent.ToString(), new UTF8Encoding(false));
|
||||
|
||||
// 使用Win7兼容的启动方式
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
Arguments = $"/c \"{batchFilePath}\"",
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
WindowStyle = ProcessWindowStyle.Hidden
|
||||
FileName = batchFilePath,
|
||||
CreateNoWindow = false, // Win7下设为false更稳定
|
||||
UseShellExecute = true, // Win7下必须使用Shell执行
|
||||
WindowStyle = ProcessWindowStyle.Minimized,
|
||||
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)
|
||||
{
|
||||
UpdateStatus($"创建替换脚本时出错: {ex.Message}");
|
||||
HandleError("创建文件替换脚本 (CreateReplaceScriptForAll)", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化程序使用的临时目录,用于存储下载过程中的临时文件
|
||||
/// </summary>
|
||||
private void InitializeTempDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
_tempDirectory = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Temp",
|
||||
"CheckDownload"
|
||||
);
|
||||
|
||||
if (!Directory.Exists(_tempDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(_tempDirectory);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UpdateStatus($"初始化临时目录失败: {ex.Message}");
|
||||
}
|
||||
private void InitializeTempDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
string exeDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
|
||||
_tempDirectory = Path.Combine(exeDir, "Temp");
|
||||
|
||||
if (File.Exists(_tempDirectory))
|
||||
{
|
||||
throw new IOException($"无法创建目录,因为已存在同名文件: {_tempDirectory}");
|
||||
}
|
||||
|
||||
if (!Directory.Exists(_tempDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(_tempDirectory);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("初始化临时目录 (InitializeTempDirectory)", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1076,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>
|
||||
/// 从阿里云OSS下载文件,包含SDK直连和IP直连两种备用方案
|
||||
/// </summary>
|
||||
@@ -1314,7 +1452,7 @@ namespace CheckDownload
|
||||
string tempPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||
"Temp",
|
||||
"CheckDownload"
|
||||
_appName
|
||||
);
|
||||
|
||||
if (Directory.Exists(tempPath))
|
||||
@@ -1496,10 +1634,21 @@ namespace CheckDownload
|
||||
{
|
||||
await localStream.WriteAsync(buffer, 0, bytesRead);
|
||||
totalRead += bytesRead;
|
||||
if (totalBytes.HasValue)
|
||||
|
||||
// === 新增: 统计整体下载量 ===
|
||||
Interlocked.Add(ref _totalDownloadedBytes, bytesRead);
|
||||
Interlocked.Add(ref _bytesSinceLastSpeedCalc, bytesRead);
|
||||
|
||||
// === 修改: 显示整体下载进度和速度(每 500ms 更新一次,避免频繁刷新) ===
|
||||
if (DateTime.UtcNow - _lastSpeedUpdateTime > TimeSpan.FromMilliseconds(500))
|
||||
{
|
||||
string progressText = $"{FormatBytes(totalRead)}/{FormatBytes(totalBytes.Value)}";
|
||||
UpdateSize(progressText);
|
||||
lock (_speedLock)
|
||||
{
|
||||
if (DateTime.UtcNow - _lastSpeedUpdateTime > TimeSpan.FromMilliseconds(500))
|
||||
{
|
||||
UpdateOverallSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1521,7 +1670,7 @@ namespace CheckDownload
|
||||
dblSByte = bytes / 1024.0;
|
||||
}
|
||||
}
|
||||
return $"{dblSByte:0.##}{suffixes[i]}";
|
||||
return $"{dblSByte:0.0}{suffixes[i]}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1540,22 +1689,17 @@ namespace CheckDownload
|
||||
await Task.Run(() => {
|
||||
try
|
||||
{
|
||||
string sevenZipDllPath = Extract7zDll();
|
||||
if (string.IsNullOrEmpty(sevenZipDllPath))
|
||||
{
|
||||
throw new Exception("无法提取7z.dll,解压中止。");
|
||||
}
|
||||
|
||||
string extractionPath = Path.GetDirectoryName(sevenZipFile);
|
||||
string tempExtractionDir = Path.Combine(_tempDirectory, Path.GetFileNameWithoutExtension(sevenZipFile) + "_temp");
|
||||
|
||||
|
||||
if (Directory.Exists(tempExtractionDir))
|
||||
{
|
||||
Directory.Delete(tempExtractionDir, true);
|
||||
}
|
||||
Directory.CreateDirectory(tempExtractionDir);
|
||||
|
||||
using (var archiveFile = new ArchiveFile(sevenZipFile, sevenZipDllPath))
|
||||
// 使用 SevenZipExtractor 解压文件到临时目录
|
||||
using (var archiveFile = new ArchiveFile(sevenZipFile, _sevenZipDllPath))
|
||||
{
|
||||
archiveFile.Extract(tempExtractionDir, true);
|
||||
}
|
||||
@@ -1579,17 +1723,10 @@ namespace CheckDownload
|
||||
}
|
||||
|
||||
Directory.Delete(tempExtractionDir, true);
|
||||
|
||||
UpdateStatus("为解压的程序设置管理员权限...");
|
||||
var exeFiles = Directory.GetFiles(extractionPath, "*.exe", SearchOption.AllDirectories);
|
||||
foreach (var exeFile in exeFiles)
|
||||
{
|
||||
SetRunAsAdminCompatibility(exeFile);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"解压失败: {ex.Message}");
|
||||
throw new Exception("DecompressTim7zAsync: 解压失败。", ex);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1599,8 +1736,7 @@ namespace CheckDownload
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UpdateStatus($"处理 tim.7z 时出错: {ex.Message}");
|
||||
await Task.Delay(3000);
|
||||
throw new Exception("DecompressTim7zAsync: 处理 tim.7z 时出错。", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1608,69 +1744,6 @@ namespace CheckDownload
|
||||
/// 为指定程序路径在注册表中设置"以管理员身份运行"的兼容性标志。
|
||||
/// </summary>
|
||||
/// <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
|
||||
private bool IsDatabaseFile(string relativePath)
|
||||
{
|
||||
@@ -1678,17 +1751,34 @@ namespace CheckDownload
|
||||
return ext == ".db" || ext == ".db3";
|
||||
}
|
||||
|
||||
private void KillProcessIfRunning(string exeRelativePath)
|
||||
private void KillProcessIfRunning(string exePath)
|
||||
{
|
||||
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))
|
||||
{
|
||||
if (proc.Id == _currentProcessId) continue;
|
||||
try
|
||||
{
|
||||
proc.Kill();
|
||||
proc.WaitForExit(5000);
|
||||
string runningProcessDir = Path.GetDirectoryName(proc.MainModule.FileName);
|
||||
if (string.Equals(runningProcessDir, targetExeDir, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
proc.Kill();
|
||||
proc.WaitForExit(5000);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
@@ -1700,20 +1790,173 @@ namespace CheckDownload
|
||||
{
|
||||
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.WaitForExit(5000);
|
||||
}
|
||||
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()
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
double intervalSeconds = (now - _lastSpeedUpdateTime).TotalSeconds;
|
||||
if (intervalSeconds <= 0) intervalSeconds = 0.1; // 防止除零
|
||||
|
||||
// 读取并清零自上次计算以来的字节数
|
||||
long intervalBytes = Interlocked.Exchange(ref _bytesSinceLastSpeedCalc, 0);
|
||||
|
||||
double bytesPerSec = intervalBytes / intervalSeconds;
|
||||
|
||||
long downloaded = Interlocked.Read(ref _totalDownloadedBytes);
|
||||
|
||||
string speedText = $"{FormatBytes((long)bytesPerSec)}/s";
|
||||
string sizeText = $"{FormatBytes(downloaded)}({speedText})";
|
||||
|
||||
_lastSpeedUpdateTime = now;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,120 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
Reference in New Issue
Block a user