966 lines
38 KiB
C#
966 lines
38 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.Data;
|
||
using System.Diagnostics;
|
||
using System.Diagnostics.CodeAnalysis;
|
||
using System.Drawing;
|
||
using System.Globalization;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Net;
|
||
using System.Net.Http;
|
||
using System.Runtime.InteropServices;
|
||
using System.Security.Cryptography;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows.Forms;
|
||
using Aliyun.OSS;
|
||
using Aliyun.OSS.Common;
|
||
using Newtonsoft.Json;
|
||
using Newtonsoft.Json.Linq;
|
||
// 蓝奏库
|
||
using LanzouCloudSolve;
|
||
|
||
namespace CheckDownload
|
||
{
|
||
public partial class Update : Form
|
||
{
|
||
// MD5文件名称
|
||
private const string Md5File = "md5.json";
|
||
// 阿里云OSS访问地址
|
||
private const string OssEndpoint = "oss-cn-hongkong.aliyuncs.com";
|
||
// 阿里云OSS存储空间名称
|
||
private const string OssBucketName = "suwin-oss";
|
||
// 阿里云OSS访问密钥ID
|
||
private const string OssAccessKeyId = "LTAI5tCwRcL5LUgyHB2j7w82";
|
||
// 阿里云OSS访问密钥Secret
|
||
private const string OssAccessKeySecret = "7ClQns3wz6psmIp9T2OfuEn3tpzrCK";
|
||
// 用于存储下载的文件数据
|
||
private Dictionary<string, (byte[] Data, string ExpectedMd5)> _downloadedFiles = new Dictionary<string, (byte[], string)>();
|
||
// 已完成的下载数量
|
||
private int _completedCount = 0;
|
||
// 总下载数量
|
||
private int _totalCount = 0;
|
||
// 临时文件夹路径
|
||
private string _tempDirectory;
|
||
|
||
// 初始化窗体
|
||
public Update()
|
||
{
|
||
InitializeComponent();
|
||
ConfigureProgressBar();
|
||
InitializeTempDirectory();
|
||
|
||
// 注册应用程序退出时清理临时文件
|
||
Application.ApplicationExit += (s, e) => CleanupTempDirectory();
|
||
}
|
||
|
||
// 配置进度条
|
||
private void ConfigureProgressBar()
|
||
{
|
||
Update_Pro.Minimum = 0;
|
||
Update_Pro.Maximum = 100;
|
||
Update_Pro.Value = 0;
|
||
Update_Pro.Step = 1;
|
||
}
|
||
|
||
private void UpdateProgressValue(int percentage)
|
||
{
|
||
if (percentage < 0) percentage = 0;
|
||
if (percentage > 100) percentage = 100;
|
||
|
||
if (this.InvokeRequired)
|
||
{
|
||
this.Invoke((MethodInvoker)delegate
|
||
{
|
||
Update_Pro.Value = Math.Min(Math.Max(percentage, Update_Pro.Minimum), Update_Pro.Maximum);
|
||
Application.DoEvents();
|
||
});
|
||
}
|
||
else
|
||
{
|
||
Update_Pro.Value = Math.Min(Math.Max(percentage, Update_Pro.Minimum), Update_Pro.Maximum);
|
||
Application.DoEvents();
|
||
}
|
||
}
|
||
|
||
private void UpdateStatus(string message)
|
||
{
|
||
if (this.InvokeRequired)
|
||
{
|
||
this.Invoke((MethodInvoker)delegate { Status_Box.Text = message; });
|
||
}
|
||
else
|
||
{
|
||
Status_Box.Text = message;
|
||
}
|
||
}
|
||
|
||
public async void Update_Load(object sender, EventArgs e)
|
||
{
|
||
PositionFormToBottomRight();
|
||
try
|
||
{
|
||
await UpdateFile();
|
||
}
|
||
finally
|
||
{
|
||
// 确保在更新完成后清理临时文件
|
||
CleanupTempDirectory();
|
||
}
|
||
}
|
||
|
||
private void PositionFormToBottomRight()
|
||
{
|
||
Rectangle workingArea = Screen.GetWorkingArea(this);
|
||
this.Location = new Point(workingArea.Right - this.Width, workingArea.Bottom - this.Height);
|
||
}
|
||
|
||
private async Task UpdateFile()
|
||
{
|
||
try
|
||
{
|
||
UpdateStatus("下载在线MD5文件并读取...");
|
||
string tempFilePath = Path.Combine(_tempDirectory, Md5File);
|
||
// 使用带备用方案的下载方法
|
||
if (!await DownloadFileWithFallback(Md5File, tempFilePath))
|
||
{
|
||
UpdateStatus("下载在线MD5文件失败");
|
||
await Task.Delay(3000);
|
||
this.Close();
|
||
return;
|
||
}
|
||
var onlineData = ReadOnlineMd5File(tempFilePath);
|
||
if (!ValidateOnlineData(onlineData.Version, onlineData.Md5, onlineData.Data))
|
||
{
|
||
UpdateStatus("在线MD5文件无效");
|
||
await Task.Delay(3000);
|
||
this.Close();
|
||
return;
|
||
}
|
||
|
||
UpdateStatus("比较本地和在线MD5文件...");
|
||
var compareResult = CompareMd5Data(onlineData.Data);
|
||
if (compareResult.Count == 0)
|
||
{
|
||
UpdateStatus("所有文件都是最新的,无需更新");
|
||
UpdateProgressValue(100);
|
||
await Task.Delay(2000);
|
||
this.Close();
|
||
return;
|
||
}
|
||
|
||
UpdateStatus("下载并验证文件...");
|
||
_totalCount = compareResult.Count;
|
||
await DownloadAndVerifyFiles(compareResult);
|
||
UpdateProgressValue(100);
|
||
|
||
UpdateStatus("更新完成");
|
||
await Task.Delay(1000);
|
||
this.Close();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateStatus($"更新失败: {ex.Message}");
|
||
await Task.Delay(3000);
|
||
this.Close();
|
||
}
|
||
}
|
||
|
||
private (string Version, string Md5, JObject Data) ReadOnlineMd5File(string filePath)
|
||
{
|
||
try
|
||
{
|
||
using (var reader = new StreamReader(filePath))
|
||
{
|
||
string json = reader.ReadToEnd();
|
||
var parsed = JObject.Parse(json);
|
||
string version = parsed["version"]?.ToString();
|
||
var data = (JObject)parsed["data"];
|
||
string jsonMd5 = CalculateMD5(json);
|
||
return (version, jsonMd5, data);
|
||
}
|
||
}
|
||
catch (Exception ex) when (ex is OssException || ex is JsonException)
|
||
{
|
||
UpdateStatus($"读取在线MD5文件失败: {ex.Message}");
|
||
return (null, null, null);
|
||
}
|
||
}
|
||
|
||
private bool ValidateOnlineData(string version, string md5, JObject data)
|
||
{
|
||
if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(md5) || data == null)
|
||
{
|
||
UpdateStatus("在线MD5文件无效");
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
private Dictionary<string, string> CompareMd5Data(JObject onlineData, string currentPath = "")
|
||
{
|
||
var differences = new Dictionary<string, string>();
|
||
|
||
foreach (var onlineProperty in onlineData.Properties())
|
||
{
|
||
string key = onlineProperty.Name;
|
||
JToken onlineValue = onlineProperty.Value;
|
||
string fullPath = string.IsNullOrEmpty(currentPath) ? key : $"{currentPath}/{key}";
|
||
|
||
if (onlineValue.Type == JTokenType.String)
|
||
{
|
||
string expectedMd5 = onlineValue.ToString();
|
||
if (!File.Exists(fullPath))
|
||
{
|
||
if (!differences.ContainsKey(fullPath))
|
||
{
|
||
differences[fullPath] = expectedMd5;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
string localMd5 = CalculateMD5(File.ReadAllBytes(fullPath));
|
||
if (localMd5 != expectedMd5)
|
||
{
|
||
if (!differences.ContainsKey(fullPath))
|
||
{
|
||
differences[fullPath] = expectedMd5;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else if (onlineValue.Type == JTokenType.Object)
|
||
{
|
||
var subDifferences = CompareMd5Data((JObject)onlineValue, fullPath);
|
||
foreach (var diff in subDifferences)
|
||
{
|
||
if (!differences.ContainsKey(diff.Key))
|
||
{
|
||
differences[diff.Key] = diff.Value;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return differences;
|
||
}
|
||
|
||
private async Task DownloadAndVerifyFiles(Dictionary<string, string> fileList)
|
||
{
|
||
try
|
||
{
|
||
OssClient client = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret);
|
||
_totalCount = fileList.Count;
|
||
_completedCount = 0;
|
||
_downloadedFiles.Clear();
|
||
|
||
var options = new ParallelOptions { MaxDegreeOfParallelism = 4 };
|
||
var failedFiles = new Dictionary<string, string>(); // 记录下载失败的文件
|
||
|
||
await Task.Run(() =>
|
||
{
|
||
Parallel.ForEach(fileList, options, (file, state) =>
|
||
{
|
||
string filePath = file.Key;
|
||
string expectedMd5 = file.Value;
|
||
|
||
try
|
||
{
|
||
string ossKey = $"File/{expectedMd5}";
|
||
var obj = client.GetObject(OssBucketName, ossKey);
|
||
|
||
// 在临时目录中创建对应的目录结构
|
||
string tempFilePath = Path.Combine(_tempDirectory, filePath);
|
||
string tempDir = Path.GetDirectoryName(tempFilePath);
|
||
if (!Directory.Exists(tempDir))
|
||
{
|
||
Directory.CreateDirectory(tempDir);
|
||
}
|
||
|
||
// 将文件保存到临时目录
|
||
using (var fileStream = File.Create(tempFilePath))
|
||
{
|
||
obj.Content.CopyTo(fileStream);
|
||
}
|
||
|
||
// 记录文件信息
|
||
lock (_downloadedFiles)
|
||
{
|
||
_downloadedFiles[filePath] = (null, expectedMd5);
|
||
}
|
||
|
||
Interlocked.Increment(ref _completedCount);
|
||
int progress = (int)((double)_completedCount / _totalCount * 95);
|
||
UpdateProgressValue(progress);
|
||
UpdateStatus($"已下载 {_completedCount}/{_totalCount}");
|
||
}
|
||
catch (Exception ex) when (ex is OssException || ex is WebException)
|
||
{
|
||
UpdateStatus($"使用备用方案尝试...");
|
||
string ossKey = $"File/{expectedMd5}";
|
||
string tempFilePath = Path.Combine(_tempDirectory, filePath);
|
||
bool fallbackResult = DownloadWithFallback(ossKey, tempFilePath);
|
||
if (fallbackResult)
|
||
{
|
||
Interlocked.Increment(ref _completedCount);
|
||
int progress = (int)((double)_completedCount / _totalCount * 95);
|
||
UpdateProgressValue(progress);
|
||
UpdateStatus($"已下载 {_completedCount}/{_totalCount}");
|
||
// 记录文件信息
|
||
lock (_downloadedFiles)
|
||
{
|
||
_downloadedFiles[filePath] = (null, expectedMd5);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
UpdateStatus($"备用方案已执行");
|
||
// 记录失败的文件,稍后重试
|
||
lock (failedFiles)
|
||
{
|
||
failedFiles[filePath] = expectedMd5;
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateStatus($"下载文件 {filePath} 失败: {ex.Message}");
|
||
// 记录失败的文件,稍后重试
|
||
lock (failedFiles)
|
||
{
|
||
failedFiles[filePath] = expectedMd5;
|
||
}
|
||
}
|
||
});
|
||
});
|
||
|
||
// 如果有失败的文件,进行重试
|
||
if (failedFiles.Count > 0)
|
||
{
|
||
UpdateStatus($"开始重试 {failedFiles.Count} 个失败的文件...");
|
||
await RetryFailedFiles(failedFiles);
|
||
}
|
||
|
||
if (_downloadedFiles.Count != fileList.Count)
|
||
{
|
||
throw new Exception($"部分文件下载失败,成功 {_downloadedFiles.Count}/{fileList.Count}");
|
||
}
|
||
|
||
VerifyAndSaveAllFiles();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateStatus($"下载过程出错: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重试下载失败的文件
|
||
/// </summary>
|
||
/// <param name="failedFiles">失败的文件字典</param>
|
||
private async Task RetryFailedFiles(Dictionary<string, string> failedFiles)
|
||
{
|
||
const int maxRetries = 2; // 最大重试次数
|
||
var retryFiles = new Dictionary<string, string>(failedFiles);
|
||
|
||
for (int retryCount = 1; retryCount <= maxRetries; retryCount++)
|
||
{
|
||
if (retryFiles.Count == 0) break;
|
||
|
||
UpdateStatus($"第 {retryCount} 次重试,剩余 {retryFiles.Count} 个文件...");
|
||
var currentRetryFiles = new Dictionary<string, string>(retryFiles);
|
||
retryFiles.Clear();
|
||
|
||
foreach (var file in currentRetryFiles)
|
||
{
|
||
string filePath = file.Key;
|
||
string expectedMd5 = file.Value;
|
||
|
||
try
|
||
{
|
||
UpdateStatus($"重试下载: {filePath}");
|
||
|
||
// 先尝试主下载
|
||
bool success = false;
|
||
try
|
||
{
|
||
OssClient client = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret);
|
||
string ossKey = $"File/{expectedMd5}";
|
||
var obj = client.GetObject(OssBucketName, ossKey);
|
||
|
||
string tempFilePath = Path.Combine(_tempDirectory, filePath);
|
||
string tempDir = Path.GetDirectoryName(tempFilePath);
|
||
if (!Directory.Exists(tempDir))
|
||
{
|
||
Directory.CreateDirectory(tempDir);
|
||
}
|
||
|
||
using (var fileStream = File.Create(tempFilePath))
|
||
{
|
||
obj.Content.CopyTo(fileStream);
|
||
}
|
||
success = true;
|
||
}
|
||
catch (Exception ex) when (ex is OssException || ex is WebException)
|
||
{
|
||
// 主下载失败,尝试备用方案
|
||
string ossKey = $"File/{expectedMd5}";
|
||
string tempFilePath = Path.Combine(_tempDirectory, filePath);
|
||
success = DownloadWithFallback(ossKey, tempFilePath);
|
||
}
|
||
|
||
if (success)
|
||
{
|
||
lock (_downloadedFiles)
|
||
{
|
||
_downloadedFiles[filePath] = (null, expectedMd5);
|
||
}
|
||
Interlocked.Increment(ref _completedCount);
|
||
int progress = (int)((double)_completedCount / _totalCount * 95);
|
||
UpdateProgressValue(progress);
|
||
UpdateStatus($"重试成功: {filePath}");
|
||
}
|
||
else
|
||
{
|
||
retryFiles[filePath] = expectedMd5;
|
||
UpdateStatus($"重试失败: {filePath}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
retryFiles[filePath] = expectedMd5;
|
||
UpdateStatus($"重试异常: {filePath} - {ex.Message}");
|
||
}
|
||
|
||
// 重试间隔,避免过于频繁
|
||
await Task.Delay(1000);
|
||
}
|
||
|
||
if (retryFiles.Count > 0 && retryCount < maxRetries)
|
||
{
|
||
UpdateStatus($"等待 3 秒后进行第 {retryCount + 1} 次重试...");
|
||
await Task.Delay(3000);
|
||
}
|
||
}
|
||
|
||
if (retryFiles.Count > 0)
|
||
{
|
||
UpdateStatus($"重试完成,仍有 {retryFiles.Count} 个文件下载失败");
|
||
}
|
||
else
|
||
{
|
||
UpdateStatus("所有文件重试成功!");
|
||
}
|
||
}
|
||
|
||
private async Task<List<string>> GetIpAddressesForDomain(string domain)
|
||
{
|
||
try
|
||
{
|
||
using (var httpClient = new HttpClient())
|
||
{
|
||
var request = new HttpRequestMessage(HttpMethod.Get, $"https://223.5.5.5/resolve?name={domain}&type=1&short=1");
|
||
request.Headers.Add("User-Agent", "Apifox/1.0.0 (https://apifox.com)");
|
||
request.Headers.Add("Accept", "*/*");
|
||
request.Headers.Host = "223.5.5.5";
|
||
request.Headers.Add("Connection", "keep-alive");
|
||
|
||
var response = await httpClient.SendAsync(request);
|
||
response.EnsureSuccessStatusCode();
|
||
|
||
string responseBody = await response.Content.ReadAsStringAsync();
|
||
return JsonConvert.DeserializeObject<List<string>>(responseBody);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateStatus($"获取域名IP失败: {domain} - {ex.Message}");
|
||
return new List<string>();
|
||
}
|
||
}
|
||
|
||
private bool DownloadWithFallback(string ossKey, string localPath)
|
||
{
|
||
try
|
||
{
|
||
UpdateStatus($"备用方案开始下载...");
|
||
// 1. 用 SDK 生成带签名的 URL
|
||
OssClient fallbackClient = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret);
|
||
var req = new GeneratePresignedUriRequest(OssBucketName, ossKey, SignHttpMethod.Get)
|
||
{
|
||
Expiration = DateTime.Now.AddMinutes(10)
|
||
};
|
||
var signedUrl = fallbackClient.GeneratePresignedUri(req).ToString();
|
||
var signedUri = new Uri(signedUrl);
|
||
var domain = signedUri.Host;
|
||
List<string> ips = GetIpAddressesForDomain(domain).GetAwaiter().GetResult();
|
||
if (ips == null || ips.Count == 0)
|
||
{
|
||
UpdateStatus($"备用方案无法获取IP地址");
|
||
return false;
|
||
}
|
||
foreach (var ip in ips)
|
||
{
|
||
try
|
||
{
|
||
var ipUrl = signedUrl.Replace(signedUri.Host, ip);
|
||
using (var httpClient = new HttpClient())
|
||
{
|
||
var request = new HttpRequestMessage(HttpMethod.Get, ipUrl);
|
||
request.Headers.Host = signedUri.Host;
|
||
var response = httpClient.SendAsync(request).GetAwaiter().GetResult();
|
||
response.EnsureSuccessStatusCode();
|
||
byte[] fileData = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult();
|
||
string localDir = Path.GetDirectoryName(localPath);
|
||
if (!Directory.Exists(localDir))
|
||
{
|
||
Directory.CreateDirectory(localDir);
|
||
}
|
||
File.WriteAllBytes(localPath, fileData);
|
||
UpdateStatus($"备用方案下载成功");
|
||
return true;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateStatus($"备用方案用IP下载失败");
|
||
}
|
||
}
|
||
UpdateStatus($"备用方案所有IP均尝试失败");
|
||
return false;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateStatus($"备用方案下载失败");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
[DllImport("kernel32.dll", SetLastError = true)]
|
||
[return: MarshalAs(UnmanagedType.Bool)]
|
||
private static extern bool GetProcessHandleCount(IntPtr hProcess, out uint pdwHandleCount);
|
||
|
||
private void VerifyAndSaveAllFiles()
|
||
{
|
||
UpdateStatus("正在校验文件...");
|
||
var failedFiles = new List<string>();
|
||
int processedCount = 0;
|
||
int totalFiles = _downloadedFiles.Count;
|
||
|
||
foreach (var item in _downloadedFiles)
|
||
{
|
||
string relativePath = item.Key;
|
||
string expectedMd5 = item.Value.ExpectedMd5;
|
||
string tempFilePath = Path.Combine(_tempDirectory, relativePath);
|
||
|
||
try
|
||
{
|
||
processedCount++;
|
||
UpdateStatus($"正在校验和保存文件 ({processedCount}/{totalFiles}): {relativePath}");
|
||
|
||
// 计算临时文件的MD5
|
||
string actualMd5 = CalculateMD5(File.ReadAllBytes(tempFilePath));
|
||
if (actualMd5 != expectedMd5.ToLower())
|
||
{
|
||
throw new Exception($"MD5校验失败 (期望: {expectedMd5}, 实际: {actualMd5})");
|
||
}
|
||
|
||
string localPath = Path.Combine(Application.StartupPath, relativePath);
|
||
string localDir = Path.GetDirectoryName(localPath);
|
||
|
||
// 确保目标目录存在
|
||
if (!Directory.Exists(localDir))
|
||
{
|
||
Directory.CreateDirectory(localDir);
|
||
}
|
||
|
||
// 尝试移动文件,如果文件被占用则尝试解锁
|
||
if (!TryMoveFile(tempFilePath, localPath))
|
||
{
|
||
// 如果无法移动文件,则使用备用文件名
|
||
string backupPath = localPath + ".new";
|
||
File.Move(tempFilePath, backupPath);
|
||
CreateReplaceScript(localPath, backupPath);
|
||
UpdateStatus($"文件 {relativePath} 正在被占用,将在程序重启后更新");
|
||
}
|
||
else
|
||
{
|
||
UpdateStatus($"文件 {relativePath} 已更新");
|
||
}
|
||
|
||
int percentage = 95 + (int)(processedCount * 5.0 / totalFiles);
|
||
UpdateProgressValue(Math.Min(100, percentage));
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateStatus($"文件校验失败: {relativePath} - {ex.Message}");
|
||
failedFiles.Add(relativePath);
|
||
}
|
||
}
|
||
|
||
foreach (var failedFile in failedFiles)
|
||
{
|
||
_downloadedFiles.Remove(failedFile);
|
||
}
|
||
|
||
if (failedFiles.Count > 0)
|
||
{
|
||
throw new Exception($"{failedFiles.Count}个文件校验失败");
|
||
}
|
||
else
|
||
{
|
||
UpdateStatus("所有文件校验和保存成功");
|
||
UpdateProgressValue(100);
|
||
}
|
||
}
|
||
|
||
private bool TryMoveFile(string sourcePath, string targetPath)
|
||
{
|
||
try
|
||
{
|
||
// 直接尝试移动文件
|
||
File.Move(sourcePath, targetPath);
|
||
return true;
|
||
}
|
||
catch (IOException)
|
||
{
|
||
// 文件被占用,尝试解锁
|
||
UpdateStatus($"文件被占用,尝试解锁...");
|
||
|
||
try
|
||
{
|
||
// 尝试找出占用文件的进程
|
||
var processes = Process.GetProcesses();
|
||
foreach (var process in processes)
|
||
{
|
||
try
|
||
{
|
||
// 跳过当前进程
|
||
if (process.Id == Process.GetCurrentProcess().Id)
|
||
continue;
|
||
|
||
// 检查进程是否可能占用了文件
|
||
string processName = process.ProcessName.ToLower();
|
||
if (processName.Contains("explorer") ||
|
||
processName.Contains("chrome") ||
|
||
processName.Contains("edge") ||
|
||
processName.Contains("firefox") ||
|
||
processName.Contains("ieplore") ||
|
||
processName.Contains("winword") ||
|
||
processName.Contains("excel") ||
|
||
processName.Contains("powerpnt") ||
|
||
processName.Contains("acrobat") ||
|
||
processName.Contains("reader"))
|
||
{
|
||
// 跳过常见的系统进程
|
||
continue;
|
||
}
|
||
|
||
// 尝试获取进程句柄数量,如果句柄数量较多,可能正在使用文件
|
||
uint handleCount = 0;
|
||
if (GetProcessHandleCount(process.Handle, out handleCount) && handleCount > 0)
|
||
{
|
||
// 尝试关闭进程
|
||
UpdateStatus($"尝试关闭可能占用文件的进程: {process.ProcessName}");
|
||
process.CloseMainWindow();
|
||
|
||
// 等待进程关闭
|
||
if (!process.WaitForExit(1000))
|
||
{
|
||
// 如果进程没有响应,尝试强制结束
|
||
process.Kill();
|
||
}
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略处理单个进程时的错误
|
||
}
|
||
finally
|
||
{
|
||
process.Dispose();
|
||
}
|
||
}
|
||
|
||
// 再次尝试移动文件
|
||
Thread.Sleep(500); // 等待一段时间确保文件已释放
|
||
File.Move(sourcePath, targetPath);
|
||
UpdateStatus($"文件解锁成功并已更新");
|
||
return true;
|
||
}
|
||
catch
|
||
{
|
||
// 如果仍然无法移动,返回失败
|
||
return false;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateStatus($"移动文件时发生错误: {ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private void CreateReplaceScript(string originalFile, string newFile)
|
||
{
|
||
try
|
||
{
|
||
string batchFilePath = Path.Combine(Application.StartupPath, "update_files.bat");
|
||
string processId = Process.GetCurrentProcess().Id.ToString();
|
||
string escapedOriginalFile = originalFile.Replace("\\", "\\\\");
|
||
string escapedNewFile = newFile.Replace("\\", "\\\\");
|
||
|
||
// 创建批处理文件内容
|
||
StringBuilder batchContent = new StringBuilder();
|
||
batchContent.AppendLine("@echo off");
|
||
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($"del \"{escapedOriginalFile}\" /f /q");
|
||
batchContent.AppendLine($"move \"{escapedNewFile}\" \"{escapedOriginalFile}\"");
|
||
batchContent.AppendLine("del \"%~f0\" /f /q");
|
||
|
||
// 写入批处理文件
|
||
bool fileExists = File.Exists(batchFilePath);
|
||
using (StreamWriter writer = new StreamWriter(batchFilePath, fileExists))
|
||
{
|
||
if (!fileExists)
|
||
{
|
||
writer.Write(batchContent.ToString());
|
||
}
|
||
else
|
||
{
|
||
// 如果文件已存在,只添加移动文件的命令
|
||
writer.WriteLine($"del \"{escapedOriginalFile}\" /f /q");
|
||
writer.WriteLine($"move \"{escapedNewFile}\" \"{escapedOriginalFile}\"");
|
||
}
|
||
}
|
||
|
||
// 如果是第一次创建批处理文件,启动它
|
||
if (!fileExists)
|
||
{
|
||
ProcessStartInfo startInfo = new ProcessStartInfo
|
||
{
|
||
FileName = "cmd.exe",
|
||
Arguments = $"/c start \"\" /min \"{batchFilePath}\"",
|
||
CreateNoWindow = true,
|
||
UseShellExecute = true,
|
||
WindowStyle = ProcessWindowStyle.Hidden
|
||
};
|
||
Process.Start(startInfo);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateStatus($"创建替换脚本时出错: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 初始化临时目录
|
||
private void InitializeTempDirectory()
|
||
{
|
||
try
|
||
{
|
||
// 获取用户临时目录
|
||
string userTempPath = Path.Combine(
|
||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||
"Temp",
|
||
"CheckDownload"
|
||
);
|
||
|
||
// 创建临时目录
|
||
_tempDirectory = CreateTempDirectory(userTempPath);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateStatus($"初始化临时目录失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 创建临时目录
|
||
private string CreateTempDirectory(string basePath)
|
||
{
|
||
try
|
||
{
|
||
// 确保基础目录存在
|
||
if (!Directory.Exists(basePath))
|
||
{
|
||
Directory.CreateDirectory(basePath);
|
||
}
|
||
|
||
// 创建带时间戳的临时目录
|
||
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||
string tempDir = Path.Combine(basePath, $"temp_{timestamp}");
|
||
|
||
if (!Directory.Exists(tempDir))
|
||
{
|
||
Directory.CreateDirectory(tempDir);
|
||
}
|
||
|
||
return tempDir;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateStatus($"创建临时目录失败: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
// 清理临时目录
|
||
private void CleanupTempDirectory()
|
||
{
|
||
try
|
||
{
|
||
// 获取CheckDownload临时文件夹路径
|
||
string checkDownloadTempPath = Path.Combine(
|
||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||
"Temp",
|
||
"CheckDownload"
|
||
);
|
||
|
||
if (!Directory.Exists(checkDownloadTempPath))
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 删除CheckDownload临时文件夹中的所有子文件夹和文件
|
||
foreach (string dir in Directory.GetDirectories(checkDownloadTempPath))
|
||
{
|
||
try
|
||
{
|
||
Directory.Delete(dir, true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.WriteLine($"删除临时目录失败: {dir} - {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 删除CheckDownload临时文件夹中的所有文件
|
||
foreach (string file in Directory.GetFiles(checkDownloadTempPath))
|
||
{
|
||
try
|
||
{
|
||
File.Delete(file);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.WriteLine($"删除临时文件失败: {file} - {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 尝试删除CheckDownload临时文件夹本身
|
||
try
|
||
{
|
||
Directory.Delete(checkDownloadTempPath, true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.WriteLine($"删除CheckDownload临时文件夹失败: {ex.Message}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.WriteLine($"清理临时目录时发生错误: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
private string CalculateMD5(string input)
|
||
{
|
||
using (var md5 = MD5.Create())
|
||
{
|
||
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
|
||
byte[] hashBytes = md5.ComputeHash(inputBytes);
|
||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
||
}
|
||
}
|
||
|
||
private string CalculateMD5(byte[] data)
|
||
{
|
||
using (var md5 = MD5.Create())
|
||
{
|
||
byte[] hashBytes = md5.ComputeHash(data);
|
||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
||
}
|
||
}
|
||
|
||
// 通用的带备用方案的 OSS 文件下载方法
|
||
private async Task<bool> DownloadFileWithFallback(string ossKey, string localPath)
|
||
{
|
||
// 1. 先尝试用 OSS SDK 下载
|
||
try
|
||
{
|
||
OssClient client = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret);
|
||
var obj = client.GetObject(OssBucketName, ossKey);
|
||
string localDir = Path.GetDirectoryName(localPath);
|
||
if (!Directory.Exists(localDir))
|
||
{
|
||
Directory.CreateDirectory(localDir);
|
||
}
|
||
using (var fileStream = File.Create(localPath))
|
||
{
|
||
obj.Content.CopyTo(fileStream);
|
||
}
|
||
return true;
|
||
}
|
||
catch (Exception ex) when (ex is OssException || ex is WebException || ex is IOException)
|
||
{
|
||
UpdateStatus($"主下载失败,尝试备用方案...");
|
||
}
|
||
|
||
// 2. 备用方案:用 IP 下载(带签名URL)
|
||
var domain = new Uri("https://" + OssEndpoint).Host;
|
||
List<string> ips = await GetIpAddressesForDomain(domain);
|
||
if (ips == null || ips.Count == 0)
|
||
{
|
||
UpdateStatus($"无法获取IP地址");
|
||
return false;
|
||
}
|
||
|
||
// 用 SDK 生成带签名的 URL
|
||
OssClient fallbackClient = new OssClient(OssEndpoint, OssAccessKeyId, OssAccessKeySecret);
|
||
var req = new GeneratePresignedUriRequest(OssBucketName, ossKey, SignHttpMethod.Get)
|
||
{
|
||
Expiration = DateTime.Now.AddMinutes(10)
|
||
};
|
||
var signedUrl = fallbackClient.GeneratePresignedUri(req).ToString();
|
||
var signedUri = new Uri(signedUrl);
|
||
|
||
foreach (var ip in ips)
|
||
{
|
||
try
|
||
{
|
||
// 替换URL中的host为IP
|
||
var ipUrl = signedUrl.Replace(signedUri.Host, ip);
|
||
using (var httpClient = new HttpClient())
|
||
{
|
||
var request = new HttpRequestMessage(HttpMethod.Get, ipUrl);
|
||
request.Headers.Host = signedUri.Host; // Host头保持原域名
|
||
var response = await httpClient.SendAsync(request);
|
||
response.EnsureSuccessStatusCode();
|
||
byte[] fileData = await response.Content.ReadAsByteArrayAsync();
|
||
string localDir = Path.GetDirectoryName(localPath);
|
||
if (!Directory.Exists(localDir))
|
||
{
|
||
Directory.CreateDirectory(localDir);
|
||
}
|
||
File.WriteAllBytes(localPath, fileData);
|
||
return true;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UpdateStatus($"备用方案用IP下载失败");
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
} |