Compare commits
1 Commits
fb6d06c85a
...
main
Author | SHA1 | Date | |
---|---|---|---|
b76115d326 |
@@ -3,6 +3,7 @@
|
|||||||
<Assemblies>
|
<Assemblies>
|
||||||
<assembly>
|
<assembly>
|
||||||
<path>.\bin\x86\Release\CheckDownload.exe</path>
|
<path>.\bin\x86\Release\CheckDownload.exe</path>
|
||||||
|
<obfuscate>true</obfuscate>
|
||||||
</assembly>
|
</assembly>
|
||||||
</Assemblies>
|
</Assemblies>
|
||||||
<Output>
|
<Output>
|
||||||
|
626
Update.cs
626
Update.cs
@@ -1,15 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -19,13 +15,11 @@ using Aliyun.OSS;
|
|||||||
using Aliyun.OSS.Common;
|
using Aliyun.OSS.Common;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Configuration;
|
using System.Configuration;
|
||||||
using SevenZipExtractor;
|
using SevenZipExtractor;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.Win32;
|
|
||||||
|
|
||||||
namespace CheckDownload
|
namespace CheckDownload
|
||||||
{
|
{
|
||||||
@@ -55,16 +49,29 @@ namespace CheckDownload
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 竞速下载结果
|
/// 竞速下载结果
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RaceDownloadResult
|
public class RaceDownloadResult
|
||||||
{
|
{
|
||||||
public bool Success { get; set; }
|
public bool Success { get; set; }
|
||||||
public string WinningSource { get; set; }
|
public string WinningSource { get; set; }
|
||||||
public double Speed { get; set; }
|
public double Speed { get; set; }
|
||||||
public string ErrorMessage { get; set; }
|
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
|
public partial class Update : Form
|
||||||
{
|
{
|
||||||
@@ -132,9 +139,20 @@ namespace CheckDownload
|
|||||||
private readonly Dictionary<string, DownloadSourceStats> _sourceStats = new Dictionary<string, DownloadSourceStats>(); // 下载源统计
|
private readonly Dictionary<string, DownloadSourceStats> _sourceStats = new Dictionary<string, DownloadSourceStats>(); // 下载源统计
|
||||||
private readonly object _sourceStatsLock = new object(); // 源统计锁
|
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 string _updateLockFilePath;
|
||||||
|
|
||||||
|
// 互斥锁相关
|
||||||
|
private Mutex _applicationMutex;
|
||||||
|
private bool _mutexCreated = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 初始化窗体
|
/// 初始化窗体
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -149,6 +167,21 @@ namespace CheckDownload
|
|||||||
_currentProcessId = Process.GetCurrentProcess().Id;
|
_currentProcessId = Process.GetCurrentProcess().Id;
|
||||||
_baseDirectory = Application.StartupPath;
|
_baseDirectory = Application.StartupPath;
|
||||||
_updateLockFilePath = Path.Combine(_baseDirectory, "update.lock");
|
_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();
|
InitializeSourceStats();
|
||||||
ConfigureProgressBar();
|
ConfigureProgressBar();
|
||||||
}
|
}
|
||||||
@@ -400,6 +433,9 @@ namespace CheckDownload
|
|||||||
|
|
||||||
var failedFiles = new ConcurrentDictionary<string, string>();
|
var failedFiles = new ConcurrentDictionary<string, string>();
|
||||||
|
|
||||||
|
// === 新增: 在开始下载前进行源速度测试 ===
|
||||||
|
await PerformSourceSpeedTest();
|
||||||
|
|
||||||
await PerformDownloads(orderedFileList, failedFiles);
|
await PerformDownloads(orderedFileList, failedFiles);
|
||||||
|
|
||||||
if (!failedFiles.IsEmpty)
|
if (!failedFiles.IsEmpty)
|
||||||
@@ -638,7 +674,304 @@ namespace CheckDownload
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="filePath">文件的相对路径</param>
|
/// <param name="filePath">文件的相对路径</param>
|
||||||
/// <param name="expectedMd5">文件的期望MD5值,用于验证完整性</param>
|
/// <param name="expectedMd5">文件的期望MD5值,用于验证完整性</param>
|
||||||
@@ -659,18 +992,18 @@ namespace CheckDownload
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(truncatedFileName))
|
if (!string.IsNullOrWhiteSpace(truncatedFileName))
|
||||||
{
|
{
|
||||||
UpdateStatus($"竞速下载:{truncatedFileName}");
|
UpdateStatus($"智能下载:{truncatedFileName}");
|
||||||
}
|
}
|
||||||
UpdateCount($"{_completedCount + 1}/{_totalCount}");
|
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))
|
if (!string.IsNullOrWhiteSpace(truncatedFileName))
|
||||||
{
|
{
|
||||||
UpdateStatus($"下载成功({raceResult.WinningSource}):{truncatedFileName}");
|
UpdateStatus($"下载成功({downloadResult.WinningSource}):{truncatedFileName}");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -678,7 +1011,7 @@ namespace CheckDownload
|
|||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(truncatedFileName))
|
if (!string.IsNullOrWhiteSpace(truncatedFileName))
|
||||||
{
|
{
|
||||||
UpdateStatus($"下载失败:{truncatedFileName} - {raceResult.ErrorMessage}");
|
UpdateStatus($"下载失败:{truncatedFileName} - {downloadResult.ErrorMessage}");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -694,15 +1027,14 @@ namespace CheckDownload
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 多源竞速下载:同时启动多个下载任务,选择最快完成的
|
/// 智能单源下载:优先使用最佳源,失败时自动切换
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filePath">文件的相对路径</param>
|
/// <param name="filePath">文件的相对路径</param>
|
||||||
/// <param name="expectedMd5">文件的期望MD5值</param>
|
/// <param name="expectedMd5">文件的期望MD5值</param>
|
||||||
/// <param name="tempFilePath">临时文件路径</param>
|
/// <param name="tempFilePath">临时文件路径</param>
|
||||||
/// <returns>竞速下载结果</returns>
|
/// <returns>下载结果</returns>
|
||||||
private async Task<RaceDownloadResult> RaceDownloadAsync(string filePath, string expectedMd5, string tempFilePath)
|
private async Task<RaceDownloadResult> SmartDownloadAsync(string filePath, string expectedMd5, string tempFilePath)
|
||||||
{
|
{
|
||||||
var cts = new CancellationTokenSource();
|
|
||||||
var fileName = $"File/{expectedMd5}";
|
var fileName = $"File/{expectedMd5}";
|
||||||
|
|
||||||
// 确保临时文件目录存在
|
// 确保临时文件目录存在
|
||||||
@@ -712,106 +1044,108 @@ namespace CheckDownload
|
|||||||
Directory.CreateDirectory(tempDir);
|
Directory.CreateDirectory(tempDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取健康的下载源,按优先级排序
|
// 获取优先使用的下载源
|
||||||
var healthySources = GetHealthyDownloadSources();
|
var sourcesToTry = GetDownloadSourcesInOrder();
|
||||||
|
|
||||||
if (healthySources.Count == 0)
|
if (sourcesToTry.Count == 0)
|
||||||
{
|
{
|
||||||
return new RaceDownloadResult
|
return new RaceDownloadResult
|
||||||
{
|
{
|
||||||
Success = false,
|
Success = false,
|
||||||
ErrorMessage = "没有可用的健康下载源"
|
ErrorMessage = "没有可用的下载源"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var downloadTasks = new List<Task<RaceDownloadResult>>();
|
// 按顺序尝试下载源
|
||||||
|
foreach (var sourceKey in sourcesToTry)
|
||||||
// 为每个健康源创建下载任务
|
|
||||||
foreach (var sourceKey in healthySources)
|
|
||||||
{
|
{
|
||||||
downloadTasks.Add(DownloadFromSingleSourceAsync(sourceKey, fileName, tempFilePath, expectedMd5, cts.Token));
|
try
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 等待第一个成功的下载完成
|
|
||||||
var completedTask = await Task.WhenAny(downloadTasks);
|
|
||||||
var result = await completedTask;
|
|
||||||
|
|
||||||
if (result.Success)
|
|
||||||
{
|
{
|
||||||
// 取消其他正在进行的下载
|
var result = await TryDownloadFromSourceAsync(sourceKey, fileName, tempFilePath, expectedMd5);
|
||||||
cts.Cancel();
|
|
||||||
|
|
||||||
// 记录成功统计
|
if (result.Success)
|
||||||
RecordDownloadResult(result.WinningSource, true, result.Speed);
|
|
||||||
|
|
||||||
// 等待其他任务取消(不超过2秒)
|
|
||||||
var remainingTasks = downloadTasks.Where(t => t != completedTask).ToArray();
|
|
||||||
if (remainingTasks.Length > 0)
|
|
||||||
{
|
{
|
||||||
try
|
// 下载成功,记录统计并返回
|
||||||
|
RecordDownloadResult(sourceKey, true, result.Speed);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 下载失败,记录统计
|
||||||
|
RecordDownloadResult(sourceKey, false, 0);
|
||||||
|
|
||||||
|
// 如果不是最后一个源,考虑是否需要切换首选源
|
||||||
|
if (sourceKey == _preferredSource && sourcesToTry.Count > 1)
|
||||||
{
|
{
|
||||||
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(2));
|
ConsiderSwitchingPreferredSource();
|
||||||
var finishedTask = await Task.WhenAny(Task.WhenAll(remainingTasks), timeoutTask);
|
|
||||||
if (finishedTask == timeoutTask)
|
|
||||||
{
|
|
||||||
// 超时,任务可能仍在运行但我们不再等待
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// 忽略取消异常
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
// 如果第一个完成的任务失败了,等待其他任务
|
|
||||||
var allResults = await Task.WhenAll(downloadTasks);
|
|
||||||
var successResult = allResults.FirstOrDefault(r => r.Success);
|
|
||||||
|
|
||||||
if (successResult != null)
|
|
||||||
{
|
{
|
||||||
RecordDownloadResult(successResult.WinningSource, true, successResult.Speed);
|
RecordDownloadResult(sourceKey, false, 0);
|
||||||
return successResult;
|
|
||||||
|
// 如果是首选源失败,考虑切换
|
||||||
|
if (sourceKey == _preferredSource && sourcesToTry.Count > 1)
|
||||||
|
{
|
||||||
|
ConsiderSwitchingPreferredSource();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 所有任务都失败了
|
// 所有源都失败了
|
||||||
var errorMessages = allResults.Where(r => !string.IsNullOrEmpty(r.ErrorMessage))
|
return new RaceDownloadResult
|
||||||
.Select(r => r.ErrorMessage)
|
{
|
||||||
.Distinct()
|
Success = false,
|
||||||
.Take(3);
|
ErrorMessage = "所有下载源都失败"
|
||||||
|
};
|
||||||
return new RaceDownloadResult
|
|
||||||
{
|
|
||||||
Success = false,
|
|
||||||
ErrorMessage = string.Join("; ", errorMessages)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
cts.Cancel();
|
|
||||||
return new RaceDownloadResult
|
|
||||||
{
|
|
||||||
Success = false,
|
|
||||||
ErrorMessage = ex.Message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
cts.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从单个源下载文件
|
/// 获取按优先级排序的下载源列表
|
||||||
/// </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 startTime = DateTime.UtcNow;
|
||||||
var tempFileWithSource = $"{tempFilePath}.{sourceKey}";
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -820,16 +1154,16 @@ namespace CheckDownload
|
|||||||
switch (sourceKey)
|
switch (sourceKey)
|
||||||
{
|
{
|
||||||
case "OneDrive_Main":
|
case "OneDrive_Main":
|
||||||
downloadSuccess = await DownloadFromOneDriveSourceAsync(OneDriveMainDomain, fileName, tempFileWithSource, cancellationToken);
|
downloadSuccess = await DownloadFromOneDriveSourceAsync(OneDriveMainDomain, fileName, tempFilePath, CancellationToken.None);
|
||||||
break;
|
break;
|
||||||
case "OneDrive_Backup":
|
case "OneDrive_Backup":
|
||||||
downloadSuccess = await DownloadFromOneDriveSourceAsync(OneDriveBackupDomain, fileName, tempFileWithSource, cancellationToken);
|
downloadSuccess = await DownloadFromOneDriveSourceAsync(OneDriveBackupDomain, fileName, tempFilePath, CancellationToken.None);
|
||||||
break;
|
break;
|
||||||
case "OSS_Direct":
|
case "OSS_Direct":
|
||||||
downloadSuccess = await DownloadFromOssDirectAsync(expectedMd5, tempFileWithSource, cancellationToken);
|
downloadSuccess = await DownloadFromOssDirectAsync(expectedMd5, tempFilePath, CancellationToken.None);
|
||||||
break;
|
break;
|
||||||
case "OSS_IP":
|
case "OSS_IP":
|
||||||
downloadSuccess = await DownloadFromOssIpAsync(expectedMd5, tempFileWithSource, cancellationToken);
|
downloadSuccess = await DownloadFromOssIpAsync(expectedMd5, tempFilePath, CancellationToken.None);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return new RaceDownloadResult { Success = false, ErrorMessage = $"未知的下载源: {sourceKey}" };
|
return new RaceDownloadResult { Success = false, ErrorMessage = $"未知的下载源: {sourceKey}" };
|
||||||
@@ -837,44 +1171,28 @@ namespace CheckDownload
|
|||||||
|
|
||||||
if (!downloadSuccess)
|
if (!downloadSuccess)
|
||||||
{
|
{
|
||||||
RecordDownloadResult(sourceKey, false, 0);
|
|
||||||
return new RaceDownloadResult { Success = false, ErrorMessage = $"{sourceKey} 下载失败" };
|
return new RaceDownloadResult { Success = false, ErrorMessage = $"{sourceKey} 下载失败" };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证文件完整性
|
// 验证文件完整性
|
||||||
if (!File.Exists(tempFileWithSource))
|
if (!File.Exists(tempFilePath))
|
||||||
{
|
{
|
||||||
return new RaceDownloadResult { Success = false, ErrorMessage = $"{sourceKey} 文件不存在" };
|
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))
|
if (!actualMd5.Equals(expectedMd5, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
try { File.Delete(tempFileWithSource); } catch { }
|
try { File.Delete(tempFilePath); } catch { }
|
||||||
RecordDownloadResult(sourceKey, false, 0);
|
|
||||||
return new RaceDownloadResult { Success = false, ErrorMessage = $"{sourceKey} MD5校验失败" };
|
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 fileInfo = new FileInfo(tempFilePath);
|
||||||
var duration = DateTime.UtcNow - startTime;
|
var duration = DateTime.UtcNow - startTime;
|
||||||
var speed = duration.TotalSeconds > 0 ? fileInfo.Length / duration.TotalSeconds : 0;
|
var speed = duration.TotalSeconds > 0 ? fileInfo.Length / duration.TotalSeconds : 0;
|
||||||
|
|
||||||
var sourceName = "";
|
var sourceName = GetSourceDisplayName(sourceKey);
|
||||||
lock (_sourceStatsLock)
|
|
||||||
{
|
|
||||||
if (_sourceStats.ContainsKey(sourceKey))
|
|
||||||
{
|
|
||||||
sourceName = _sourceStats[sourceKey].SourceName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new RaceDownloadResult
|
return new RaceDownloadResult
|
||||||
{
|
{
|
||||||
@@ -883,33 +1201,55 @@ namespace CheckDownload
|
|||||||
Speed = speed
|
Speed = speed
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
try { if (File.Exists(tempFileWithSource)) File.Delete(tempFileWithSource); } catch { }
|
|
||||||
return new RaceDownloadResult { Success = false, ErrorMessage = "下载被取消" };
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
try { if (File.Exists(tempFileWithSource)) File.Delete(tempFileWithSource); } catch { }
|
try { if (File.Exists(tempFilePath)) File.Delete(tempFilePath); } catch { }
|
||||||
RecordDownloadResult(sourceKey, false, 0);
|
|
||||||
return new RaceDownloadResult { Success = false, ErrorMessage = $"{sourceKey}: {ex.Message}" };
|
return new RaceDownloadResult { Success = false, ErrorMessage = $"{sourceKey}: {ex.Message}" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取健康的下载源列表,按优先级排序
|
/// 考虑是否需要切换首选下载源
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<string> GetHealthyDownloadSources()
|
private void ConsiderSwitchingPreferredSource()
|
||||||
{
|
{
|
||||||
lock (_sourceStatsLock)
|
lock (_sourceLock)
|
||||||
{
|
{
|
||||||
return _sourceStats
|
// 检查是否在冷却期内
|
||||||
.Where(kvp => kvp.Value.IsHealthy || (kvp.Value.SuccessCount == 0 && kvp.Value.FailCount < 3))
|
if (DateTime.UtcNow - _lastSourceSwitch < _sourceSwitchCooldown)
|
||||||
.OrderByDescending(kvp => kvp.Value.AverageSpeed)
|
{
|
||||||
.ThenByDescending(kvp => kvp.Value.SuccessCount)
|
return;
|
||||||
.ThenBy(kvp => kvp.Value.FailCount)
|
}
|
||||||
.Select(kvp => kvp.Key)
|
|
||||||
.ToList();
|
// 查找最佳替代源
|
||||||
|
string bestAlternative = null;
|
||||||
|
double bestSpeed = 0;
|
||||||
|
|
||||||
|
lock (_sourceStatsLock)
|
||||||
|
{
|
||||||
|
var alternatives = _sourceStats
|
||||||
|
.Where(kvp => kvp.Key != _preferredSource && kvp.Value.IsHealthy)
|
||||||
|
.OrderByDescending(kvp => kvp.Value.AverageSpeed)
|
||||||
|
.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}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user