Compare commits
1 Commits
fb6d06c85a
...
main
Author | SHA1 | Date | |
---|---|---|---|
b76115d326 |
@@ -3,6 +3,7 @@
|
||||
<Assemblies>
|
||||
<assembly>
|
||||
<path>.\bin\x86\Release\CheckDownload.exe</path>
|
||||
<obfuscate>true</obfuscate>
|
||||
</assembly>
|
||||
</Assemblies>
|
||||
<Output>
|
||||
|
592
Update.cs
592
Update.cs
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user