1 Commits

2 changed files with 484 additions and 143 deletions

View File

@@ -3,6 +3,7 @@
<Assemblies>
<assembly>
<path>.\bin\x86\Release\CheckDownload.exe</path>
<obfuscate>true</obfuscate>
</assembly>
</Assemblies>
<Output>

592
Update.cs
View File

@@ -1,15 +1,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
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;
@@ -19,13 +15,11 @@ using Aliyun.OSS;
using Aliyun.OSS.Common;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Configuration;
using SevenZipExtractor;
using System.Reflection;
using Microsoft.Win32;
namespace CheckDownload
{
@@ -66,6 +60,19 @@ namespace CheckDownload
public string ErrorMessage { get; set; }
}
/// <summary>
/// 下载源速度测试结果
/// </summary>
public class SourceSpeedTestResult
{
public string SourceKey { get; set; }
public string SourceName { get; set; }
public bool Success { get; set; }
public double Speed { get; set; } // bytes/second
public long TestTime { get; set; } // milliseconds
public string ErrorMessage { get; set; }
}
public partial class Update : Form
{
// MD5文件名称
@@ -132,9 +139,20 @@ namespace CheckDownload
private readonly Dictionary<string, DownloadSourceStats> _sourceStats = new Dictionary<string, DownloadSourceStats>(); // 下载源统计
private readonly object _sourceStatsLock = new object(); // 源统计锁
// === 新增: 智能下载源选择相关 ===
private string _preferredSource = null; // 当前首选下载源
private DateTime _lastSourceSwitch = DateTime.MinValue; // 上次切换源的时间
private readonly TimeSpan _sourceSwitchCooldown = TimeSpan.FromMinutes(1); // 切换源冷却时间
private readonly double _minDownloadSpeedThreshold = 10 * 1024; // 最低下载速度阈值 (10KB/s)
private readonly object _sourceLock = new object(); // 源选择锁
// 更新锁文件路径
private string _updateLockFilePath;
// 互斥锁相关
private Mutex _applicationMutex;
private bool _mutexCreated = false;
/// <summary>
/// 初始化窗体
/// </summary>
@@ -149,6 +167,21 @@ namespace CheckDownload
_currentProcessId = Process.GetCurrentProcess().Id;
_baseDirectory = Application.StartupPath;
_updateLockFilePath = Path.Combine(_baseDirectory, "update.lock");
// 根据当前文件夹名称决定是否创建互斥锁
string currentFolderName = Path.GetFileName(_baseDirectory);
if (!string.Equals(currentFolderName, "Resources", StringComparison.OrdinalIgnoreCase))
{
// 创建互斥锁
string mutexName = $"Global\\{_appName}_{_currentProcessId}";
_applicationMutex = new Mutex(true, mutexName, out _mutexCreated);
}
else
{
// 当前在Resources文件夹中不创建互斥锁
_mutexCreated = false;
}
InitializeSourceStats();
ConfigureProgressBar();
}
@@ -400,6 +433,9 @@ namespace CheckDownload
var failedFiles = new ConcurrentDictionary<string, string>();
// === 新增: 在开始下载前进行源速度测试 ===
await PerformSourceSpeedTest();
await PerformDownloads(orderedFileList, failedFiles);
if (!failedFiles.IsEmpty)
@@ -638,7 +674,304 @@ namespace CheckDownload
}
/// <summary>
/// 使用多源竞速下载单个文件,同时从多个源下载,选择最快的
/// 执行下载源速度测试,选择最佳下载源
/// </summary>
private async Task PerformSourceSpeedTest()
{
UpdateStatus("正在测试下载源速度...");
UpdateCount("");
UpdateSize("");
try
{
// 使用md5.json文件进行速度测试通常这个文件比较小适合测试
var testResults = new List<SourceSpeedTestResult>();
var testTasks = new List<Task<SourceSpeedTestResult>>();
// 为每个源创建测试任务
var availableSources = new[] { "OneDrive_Main", "OneDrive_Backup", "OSS_Direct", "OSS_IP" };
foreach (var sourceKey in availableSources)
{
testTasks.Add(TestSourceSpeedAsync(sourceKey, Md5File));
}
// 等待所有测试完成,但设置超时时间
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(30));
var completedTask = await Task.WhenAny(Task.WhenAll(testTasks), timeoutTask);
if (completedTask == timeoutTask)
{
UpdateStatus("速度测试超时,使用默认源顺序");
return;
}
testResults = testTasks.Select(t => t.Result).ToList();
// 选择最快的可用源
var bestSource = testResults
.Where(r => r.Success && r.Speed > 0)
.OrderByDescending(r => r.Speed)
.FirstOrDefault();
lock (_sourceLock)
{
if (bestSource != null)
{
_preferredSource = bestSource.SourceKey;
UpdateStatus($"选择最佳下载源: {bestSource.SourceName} ({FormatBytes((long)bestSource.Speed)}/s)");
// 更新源统计
lock (_sourceStatsLock)
{
if (_sourceStats.ContainsKey(bestSource.SourceKey))
{
_sourceStats[bestSource.SourceKey].RecordSuccess(bestSource.Speed);
}
}
}
else
{
UpdateStatus("所有源测试失败,将使用默认顺序");
_preferredSource = "OneDrive_Main"; // 默认源
}
}
// 显示测试结果摘要
var successfulSources = testResults.Where(r => r.Success).ToList();
if (successfulSources.Count > 1)
{
var summary = string.Join(", ", successfulSources
.OrderByDescending(r => r.Speed)
.Take(3)
.Select(r => $"{r.SourceName}:{FormatBytes((long)r.Speed)}/s"));
UpdateStatus($"速度测试完成 - {summary}");
}
await Task.Delay(2000); // 显示结果2秒
}
catch (Exception ex)
{
UpdateStatus($"速度测试异常: {ex.Message}");
lock (_sourceLock)
{
_preferredSource = "OneDrive_Main"; // 出错时使用默认源
}
}
}
/// <summary>
/// 测试单个下载源的速度
/// </summary>
/// <param name="sourceKey">下载源标识</param>
/// <param name="testFileName">用于测试的文件名</param>
/// <returns>测试结果</returns>
private async Task<SourceSpeedTestResult> TestSourceSpeedAsync(string sourceKey, string testFileName)
{
var result = new SourceSpeedTestResult
{
SourceKey = sourceKey,
SourceName = GetSourceDisplayName(sourceKey)
};
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
string tempTestFile = Path.Combine(_tempDirectory, $"speedtest_{sourceKey}_{Guid.NewGuid()}.tmp");
try
{
bool downloadSuccess = false;
long fileSize = 0;
switch (sourceKey)
{
case "OneDrive_Main":
downloadSuccess = await TestDownloadFromOneDrive(OneDriveMainDomain, testFileName, tempTestFile);
break;
case "OneDrive_Backup":
downloadSuccess = await TestDownloadFromOneDrive(OneDriveBackupDomain, testFileName, tempTestFile);
break;
case "OSS_Direct":
downloadSuccess = await TestDownloadFromOssDirect(testFileName, tempTestFile);
break;
case "OSS_IP":
downloadSuccess = await TestDownloadFromOssIp(testFileName, tempTestFile);
break;
default:
result.Success = false;
result.ErrorMessage = $"未知的下载源: {sourceKey}";
return result;
}
stopwatch.Stop();
result.TestTime = stopwatch.ElapsedMilliseconds;
if (downloadSuccess && File.Exists(tempTestFile))
{
fileSize = new FileInfo(tempTestFile).Length;
result.Speed = fileSize * 1000.0 / Math.Max(result.TestTime, 1); // bytes per second
result.Success = true;
}
else
{
result.Success = false;
result.ErrorMessage = "下载失败";
}
}
catch (Exception ex)
{
stopwatch.Stop();
result.Success = false;
result.ErrorMessage = ex.Message;
result.TestTime = stopwatch.ElapsedMilliseconds;
}
finally
{
// 清理测试文件
try
{
if (File.Exists(tempTestFile))
{
File.Delete(tempTestFile);
}
}
catch { }
}
return result;
}
/// <summary>
/// 获取下载源的显示名称
/// </summary>
private string GetSourceDisplayName(string sourceKey)
{
lock (_sourceStatsLock)
{
if (_sourceStats.ContainsKey(sourceKey))
{
return _sourceStats[sourceKey].SourceName;
}
}
switch (sourceKey)
{
case "OneDrive_Main":
return "主域名";
case "OneDrive_Backup":
return "备用域名";
case "OSS_Direct":
return "OSS直连";
case "OSS_IP":
return "OSSIP直连";
default:
return sourceKey;
}
}
/// <summary>
/// 测试从123盘下载
/// </summary>
private async Task<bool> TestDownloadFromOneDrive(string domain, string fileName, string localPath)
{
try
{
string authUrl = GenerateAuthUrl($"http://{domain}{OneDrivePath}", fileName);
var request = new HttpRequestMessage(HttpMethod.Get, authUrl) { Version = HttpVersion.Version11 };
using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode();
using (var remote = await response.Content.ReadAsStreamAsync())
using (var localFile = File.Create(localPath))
{
await remote.CopyToAsync(localFile);
}
return true;
}
}
catch
{
return false;
}
}
/// <summary>
/// 测试从OSS直连下载
/// </summary>
private async Task<bool> TestDownloadFromOssDirect(string fileName, string localPath)
{
try
{
var obj = _ossClient.GetObject(OssBucketName, fileName);
using (var fileStream = File.Create(localPath))
{
await obj.Content.CopyToAsync(fileStream);
}
return true;
}
catch
{
return false;
}
}
/// <summary>
/// 测试从OSS IP直连下载
/// </summary>
private async Task<bool> TestDownloadFromOssIp(string fileName, string localPath)
{
try
{
var domain = new Uri("https://" + OssEndpoint).Host;
List<string> ips = await GetIpAddressesForDomain(domain);
if (ips == null || ips.Count == 0)
{
return false;
}
var req = new GeneratePresignedUriRequest(OssBucketName, fileName, SignHttpMethod.Get)
{
Expiration = DateTime.Now.AddMinutes(10)
};
var signedUrl = _ossClient.GeneratePresignedUri(req).ToString();
var signedUri = new Uri(signedUrl);
// 只尝试第一个IP进行测试
if (ips.Count > 0)
{
var ipUrl = signedUrl.Replace(signedUri.Host, ips[0]);
var request = new HttpRequestMessage(HttpMethod.Get, ipUrl) { Version = HttpVersion.Version11 };
request.Headers.Host = signedUri.Host;
using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode();
using (var remote = await response.Content.ReadAsStreamAsync())
using (var localFile = File.Create(localPath))
{
await remote.CopyToAsync(localFile);
}
return true;
}
}
return false;
}
catch
{
return false;
}
}
/// <summary>
/// 使用智能单源下载替代原来的竞速下载
/// </summary>
/// <param name="filePath">文件的相对路径</param>
/// <param name="expectedMd5">文件的期望MD5值用于验证完整性</param>
@@ -659,18 +992,18 @@ namespace CheckDownload
if (!string.IsNullOrWhiteSpace(truncatedFileName))
{
UpdateStatus($"竞速下载:{truncatedFileName}");
UpdateStatus($"智能下载:{truncatedFileName}");
}
UpdateCount($"{_completedCount + 1}/{_totalCount}");
// 使用多源竞速下载
var raceResult = await RaceDownloadAsync(filePath, expectedMd5, tempFilePath);
// 使用智能单源下载
var downloadResult = await SmartDownloadAsync(filePath, expectedMd5, tempFilePath);
if (raceResult.Success)
if (downloadResult.Success)
{
if (!string.IsNullOrWhiteSpace(truncatedFileName))
{
UpdateStatus($"下载成功({raceResult.WinningSource}):{truncatedFileName}");
UpdateStatus($"下载成功({downloadResult.WinningSource}):{truncatedFileName}");
}
return true;
}
@@ -678,7 +1011,7 @@ namespace CheckDownload
{
if (!string.IsNullOrWhiteSpace(truncatedFileName))
{
UpdateStatus($"下载失败:{truncatedFileName} - {raceResult.ErrorMessage}");
UpdateStatus($"下载失败:{truncatedFileName} - {downloadResult.ErrorMessage}");
}
return false;
}
@@ -694,15 +1027,14 @@ namespace CheckDownload
}
/// <summary>
/// 多源竞速下载:同时启动多个下载任务,选择最快完成的
/// 智能单源下载:优先使用最佳源,失败时自动切换
/// </summary>
/// <param name="filePath">文件的相对路径</param>
/// <param name="expectedMd5">文件的期望MD5值</param>
/// <param name="tempFilePath">临时文件路径</param>
/// <returns>竞速下载结果</returns>
private async Task<RaceDownloadResult> RaceDownloadAsync(string filePath, string expectedMd5, string tempFilePath)
/// <returns>下载结果</returns>
private async Task<RaceDownloadResult> SmartDownloadAsync(string filePath, string expectedMd5, string tempFilePath)
{
var cts = new CancellationTokenSource();
var fileName = $"File/{expectedMd5}";
// 确保临时文件目录存在
@@ -712,106 +1044,108 @@ namespace CheckDownload
Directory.CreateDirectory(tempDir);
}
// 获取健康的下载源,按优先级排序
var healthySources = GetHealthyDownloadSources();
// 获取优先使用的下载源
var sourcesToTry = GetDownloadSourcesInOrder();
if (healthySources.Count == 0)
if (sourcesToTry.Count == 0)
{
return new RaceDownloadResult
{
Success = false,
ErrorMessage = "没有可用的健康下载源"
ErrorMessage = "没有可用的下载源"
};
}
var downloadTasks = new List<Task<RaceDownloadResult>>();
// 为每个健康源创建下载任务
foreach (var sourceKey in healthySources)
// 按顺序尝试下载源
foreach (var sourceKey in sourcesToTry)
{
downloadTasks.Add(DownloadFromSingleSourceAsync(sourceKey, fileName, tempFilePath, expectedMd5, cts.Token));
}
try
{
// 等待第一个成功的下载完成
var completedTask = await Task.WhenAny(downloadTasks);
var result = await completedTask;
var result = await TryDownloadFromSourceAsync(sourceKey, fileName, tempFilePath, expectedMd5);
if (result.Success)
{
// 取消其他正在进行的下载
cts.Cancel();
// 记录成功统计
RecordDownloadResult(result.WinningSource, true, result.Speed);
// 等待其他任务取消不超过2秒
var remainingTasks = downloadTasks.Where(t => t != completedTask).ToArray();
if (remainingTasks.Length > 0)
{
try
{
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(2));
var finishedTask = await Task.WhenAny(Task.WhenAll(remainingTasks), timeoutTask);
if (finishedTask == timeoutTask)
{
// 超时,任务可能仍在运行但我们不再等待
}
}
catch
{
// 忽略取消异常
}
}
// 下载成功,记录统计并返回
RecordDownloadResult(sourceKey, true, result.Speed);
return result;
}
// 如果第一个完成的任务失败了,等待其他任务
var allResults = await Task.WhenAll(downloadTasks);
var successResult = allResults.FirstOrDefault(r => r.Success);
if (successResult != null)
else
{
RecordDownloadResult(successResult.WinningSource, true, successResult.Speed);
return successResult;
// 下载失败,记录统计
RecordDownloadResult(sourceKey, false, 0);
// 如果不是最后一个源,考虑是否需要切换首选源
if (sourceKey == _preferredSource && sourcesToTry.Count > 1)
{
ConsiderSwitchingPreferredSource();
}
}
// 所有任务都失败了
var errorMessages = allResults.Where(r => !string.IsNullOrEmpty(r.ErrorMessage))
.Select(r => r.ErrorMessage)
.Distinct()
.Take(3);
return new RaceDownloadResult
{
Success = false,
ErrorMessage = string.Join("; ", errorMessages)
};
}
catch (Exception ex)
{
cts.Cancel();
return new RaceDownloadResult
RecordDownloadResult(sourceKey, false, 0);
// 如果是首选源失败,考虑切换
if (sourceKey == _preferredSource && sourcesToTry.Count > 1)
{
Success = false,
ErrorMessage = ex.Message
};
ConsiderSwitchingPreferredSource();
}
finally
{
cts.Dispose();
}
}
// 所有源都失败了
return new RaceDownloadResult
{
Success = false,
ErrorMessage = "所有下载源都失败"
};
}
/// <summary>
/// 从单个源下载文件
/// 获取按优先级排序的下载源列表
/// </summary>
private async Task<RaceDownloadResult> DownloadFromSingleSourceAsync(string sourceKey, string fileName, string tempFilePath, string expectedMd5, CancellationToken cancellationToken)
/// <returns>下载源列表</returns>
private List<string> GetDownloadSourcesInOrder()
{
var sources = new List<string>();
lock (_sourceLock)
{
// 首先添加首选源
if (!string.IsNullOrEmpty(_preferredSource))
{
sources.Add(_preferredSource);
}
// 然后添加其他健康的源
lock (_sourceStatsLock)
{
var healthySources = _sourceStats
.Where(kvp => kvp.Key != _preferredSource &&
(kvp.Value.IsHealthy || (kvp.Value.SuccessCount == 0 && kvp.Value.FailCount < 3)))
.OrderByDescending(kvp => kvp.Value.AverageSpeed)
.ThenByDescending(kvp => kvp.Value.SuccessCount)
.Select(kvp => kvp.Key);
sources.AddRange(healthySources);
}
// 如果没有首选源,使用默认顺序
if (sources.Count == 0)
{
sources.AddRange(new[] { "OneDrive_Main", "OneDrive_Backup", "OSS_Direct", "OSS_IP" });
}
}
return sources.Distinct().ToList();
}
/// <summary>
/// 从单个源尝试下载文件
/// </summary>
private async Task<RaceDownloadResult> TryDownloadFromSourceAsync(string sourceKey, string fileName, string tempFilePath, string expectedMd5)
{
var startTime = DateTime.UtcNow;
var tempFileWithSource = $"{tempFilePath}.{sourceKey}";
try
{
@@ -820,16 +1154,16 @@ namespace CheckDownload
switch (sourceKey)
{
case "OneDrive_Main":
downloadSuccess = await DownloadFromOneDriveSourceAsync(OneDriveMainDomain, fileName, tempFileWithSource, cancellationToken);
downloadSuccess = await DownloadFromOneDriveSourceAsync(OneDriveMainDomain, fileName, tempFilePath, CancellationToken.None);
break;
case "OneDrive_Backup":
downloadSuccess = await DownloadFromOneDriveSourceAsync(OneDriveBackupDomain, fileName, tempFileWithSource, cancellationToken);
downloadSuccess = await DownloadFromOneDriveSourceAsync(OneDriveBackupDomain, fileName, tempFilePath, CancellationToken.None);
break;
case "OSS_Direct":
downloadSuccess = await DownloadFromOssDirectAsync(expectedMd5, tempFileWithSource, cancellationToken);
downloadSuccess = await DownloadFromOssDirectAsync(expectedMd5, tempFilePath, CancellationToken.None);
break;
case "OSS_IP":
downloadSuccess = await DownloadFromOssIpAsync(expectedMd5, tempFileWithSource, cancellationToken);
downloadSuccess = await DownloadFromOssIpAsync(expectedMd5, tempFilePath, CancellationToken.None);
break;
default:
return new RaceDownloadResult { Success = false, ErrorMessage = $"未知的下载源: {sourceKey}" };
@@ -837,44 +1171,28 @@ namespace CheckDownload
if (!downloadSuccess)
{
RecordDownloadResult(sourceKey, false, 0);
return new RaceDownloadResult { Success = false, ErrorMessage = $"{sourceKey} 下载失败" };
}
// 验证文件完整性
if (!File.Exists(tempFileWithSource))
if (!File.Exists(tempFilePath))
{
return new RaceDownloadResult { Success = false, ErrorMessage = $"{sourceKey} 文件不存在" };
}
string actualMd5 = await Task.Run(() => CalculateMD5FromFile(tempFileWithSource), cancellationToken);
string actualMd5 = await Task.Run(() => CalculateMD5FromFile(tempFilePath));
if (!actualMd5.Equals(expectedMd5, StringComparison.OrdinalIgnoreCase))
{
try { File.Delete(tempFileWithSource); } catch { }
RecordDownloadResult(sourceKey, false, 0);
try { File.Delete(tempFilePath); } catch { }
return new RaceDownloadResult { Success = false, ErrorMessage = $"{sourceKey} MD5校验失败" };
}
// 移动到最终位置
if (File.Exists(tempFilePath))
{
File.Delete(tempFilePath);
}
File.Move(tempFileWithSource, tempFilePath);
// 计算下载速度
var fileInfo = new FileInfo(tempFilePath);
var duration = DateTime.UtcNow - startTime;
var speed = duration.TotalSeconds > 0 ? fileInfo.Length / duration.TotalSeconds : 0;
var sourceName = "";
lock (_sourceStatsLock)
{
if (_sourceStats.ContainsKey(sourceKey))
{
sourceName = _sourceStats[sourceKey].SourceName;
}
}
var sourceName = GetSourceDisplayName(sourceKey);
return new RaceDownloadResult
{
@@ -883,33 +1201,55 @@ namespace CheckDownload
Speed = speed
};
}
catch (OperationCanceledException)
{
try { if (File.Exists(tempFileWithSource)) File.Delete(tempFileWithSource); } catch { }
return new RaceDownloadResult { Success = false, ErrorMessage = "下载被取消" };
}
catch (Exception ex)
{
try { if (File.Exists(tempFileWithSource)) File.Delete(tempFileWithSource); } catch { }
RecordDownloadResult(sourceKey, false, 0);
try { if (File.Exists(tempFilePath)) File.Delete(tempFilePath); } catch { }
return new RaceDownloadResult { Success = false, ErrorMessage = $"{sourceKey}: {ex.Message}" };
}
}
/// <summary>
/// 获取健康的下载源列表,按优先级排序
/// 考虑是否需要切换首选下载源
/// </summary>
private List<string> GetHealthyDownloadSources()
private void ConsiderSwitchingPreferredSource()
{
lock (_sourceLock)
{
// 检查是否在冷却期内
if (DateTime.UtcNow - _lastSourceSwitch < _sourceSwitchCooldown)
{
return;
}
// 查找最佳替代源
string bestAlternative = null;
double bestSpeed = 0;
lock (_sourceStatsLock)
{
return _sourceStats
.Where(kvp => kvp.Value.IsHealthy || (kvp.Value.SuccessCount == 0 && kvp.Value.FailCount < 3))
var alternatives = _sourceStats
.Where(kvp => kvp.Key != _preferredSource && kvp.Value.IsHealthy)
.OrderByDescending(kvp => kvp.Value.AverageSpeed)
.ThenByDescending(kvp => kvp.Value.SuccessCount)
.ThenBy(kvp => kvp.Value.FailCount)
.Select(kvp => kvp.Key)
.ToList();
.FirstOrDefault();
if (alternatives.Key != null && alternatives.Value.AverageSpeed > bestSpeed)
{
bestAlternative = alternatives.Key;
bestSpeed = alternatives.Value.AverageSpeed;
}
}
// 如果找到更好的替代源,切换
if (!string.IsNullOrEmpty(bestAlternative))
{
var oldSource = GetSourceDisplayName(_preferredSource);
var newSource = GetSourceDisplayName(bestAlternative);
_preferredSource = bestAlternative;
_lastSourceSwitch = DateTime.UtcNow;
UpdateStatus($"切换下载源: {oldSource} -> {newSource}");
}
}
}