This commit is contained in:
龙腾猫跃
2026-03-08 20:56:13 +08:00
parent 2bffafcd7a
commit be67c4ccc2
27 changed files with 3090 additions and 1722 deletions
+96 -152
View File
@@ -1,5 +1,4 @@
Imports System.Net.Http.Headers
Imports System.Net.Sockets
Imports System.Net.Sockets
Imports System.Runtime.CompilerServices
Imports System.Threading.Tasks
@@ -248,7 +247,7 @@ RequestFinished:
SecretHeadersSign(Url, Request, SimulateBrowserHeaders)
'DNS 解析
CancelToken = New CancellationTokenSource(Timeout)
HostIp = DNSLookup(Request, CancelToken)
HostIp = DNSLookup(Request, CancelToken.Token)
If HostIp IsNot Nothing AndAlso Not IPReliability.ContainsKey(HostIp) Then
IPReliability(HostIp) = -0.01 '预先降低一点,这样快速的重复请求会使用不同的 IP 以提高成功率
End If
@@ -263,11 +262,12 @@ RequestFinished:
End If
End SyncLock
Response = RequestClient.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
Dim ResponseStream = Response.Content.ReadAsStreamAsync().GetAwaiter().GetResult()
Dim ResponseBytes As Byte()
Using Stream As New MemoryStream
ResponseStream.CopyToAsync(Stream, 81920, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
ResponseBytes = Stream.ToArray()
Using ResponseStream = Response.Content.ReadAsStreamAsync().GetResultWithTimeout(CancelToken, Timeout)
Using Stream As New MemoryStream
ResponseStream.CopyToAsync(Stream, 81920, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
ResponseBytes = Stream.ToArray()
End Using
End Using
'输出
If Response.IsSuccessStatusCode Then
@@ -304,10 +304,9 @@ RequestFinished:
''' <summary>
''' 进行 DNS 解析。它仅在选择的 IP 与系统默认的不一致时才对 URL 中的 Host 进行替换。
''' 返回请求时应使用的 IP;若为 IPv6,则加上了方括号。
''' 若解析失败,则返回 Nothing。
''' 返回请求时应使用的 IP。若解析失败,则返回 Nothing。
''' </summary>
Private Function DNSLookup(Request As HttpRequestMessage, CancelToken As CancellationTokenSource) As String
Private Function DNSLookup(Request As HttpRequestMessage, CancelToken As CancellationToken) As String
'若一分钟内已失败过,则不再重复尝试解析,以减少断网时的 Exception 数量
Dim Host = Request.RequestUri.Host
SyncLock DNSFailureRecord.SyncRoot
@@ -320,43 +319,36 @@ RequestFinished:
End If
End SyncLock
'初步 DNS 解析
Dim DnsTask As Task(Of IPAddress()) = Nothing
Dim Candidates As IPAddress()
Try
DnsTask = Dns.GetHostAddressesAsync(Host)
DnsTask.Wait(CancelToken.Token)
Dim AsyncResult = Dns.BeginGetHostAddresses(Host, Nothing, Nothing)
If WaitHandle.WaitAny(New WaitHandle() {AsyncResult.AsyncWaitHandle, CancelToken.WaitHandle}) Then Throw New TimeoutException("DNS 解析超时")
Candidates = Dns.EndGetHostAddresses(AsyncResult).Distinct.ToArray
Catch ex As Exception
Log(ex, $"DNS 解析失败({Host})")
DNSFailureRecord(Host) = Date.Now
Return Nothing
End Try
Dim Candidates As IPAddress() = DnsTask.Result.ToArray
If Not Candidates.Any Then
Log($"[Net] DNS 解析无结果({Host})")
Return Nothing
End If
'若同时存在 IPv4 和 IPv6 地址,仅选择其中一类(因为 GFW 可能只屏蔽了 IPv4 或 IPv6)
Dim GetIPReliability = Function(Key) IPReliability.GetOrDefault(Key.ToString, 0)
Dim Reliabilities = Candidates.ToDictionary(Function(i) i, Function(i) IPReliability.GetOrDefault(i.ToString, 0))
Dim IPv4Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetwork).ToArray
Dim IPv6Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetworkV6).ToArray
If IPv4Targets.Any AndAlso IPv6Targets.Any Then
Dim IPv4Reliability = IPv4Targets.Average(Function(i) GetIPReliability(i))
Dim IPv6Reliability = IPv6Targets.Average(Function(i) GetIPReliability(i))
Dim IPv4Reliability = IPv4Targets.Max(Function(i) Reliabilities(i))
Dim IPv6Reliability = IPv6Targets.Max(Function(i) Reliabilities(i))
If Host = "api.modrinth.com" Then IPv6Reliability -= 0.1 '让 Modrinth 优先使用 IPv4 地址(#6887)
If ModeDebug Then Log($"[Net] DNS IPv4/IPv6 选择({Host}),IPv4 {IPv4Reliability:0.000},IPv6 {IPv6Reliability:0.000}")
Candidates = If(IPv4Reliability >= IPv6Reliability, IPv4Targets, IPv6Targets)
End If
'选择可靠度最高的 IP
Dim MaxReliability As Double = Candidates.Max(Function(i) GetIPReliability(i))
Dim Target As IPAddress = Candidates.First(Function(i) GetIPReliability(i) = MaxReliability)
Dim TargetIp As String = If(Target.AddressFamily = AddressFamily.InterNetworkV6, $"[{Target}]", Target.ToString)
If Target IsNot Candidates.First Then
'优选结果与系统默认 IP 不一致,替换域名并设置 Host 头
If ModeDebug Then Log($"[Net] DNS 解析结果:{Host} → {Target},所有可能的 IP 与可靠度:{DnsTask.Result.Select(Function(i) $"{i} → {GetIPReliability(i):0.000}").Join(";")}")
Request.Headers.Host = Host
Dim Builder As New UriBuilder(Request.RequestUri)
Builder.Host = TargetIp
Request.RequestUri = Builder.Uri
End If
Return TargetIp
Dim Target As IPAddress = Candidates.MaxBy(Function(i) Reliabilities(i))
Request.Headers.Host = Request.RequestUri.Authority
Request.RequestUri = (New UriBuilder(Request.RequestUri) With {.Host = If(Target.AddressFamily = AddressFamily.InterNetworkV6, $"[{Target}]", Target.ToString)}).Uri
Return Target.ToString
End Function
Private DNSFailureRecord As New SafeDictionary(Of String, Date)
''' <summary>
@@ -369,8 +361,10 @@ RequestFinished:
''' </summary>
Private Sub RecordIPReliability(IP As String, Result As Double)
If IP Is Nothing Then Return
If Not IPReliability.ContainsKey(IP) Then IPReliability(IP) = 0
IPReliability(IP) = (IPReliability(IP) + Result) / 2
SyncLock IPReliability.SyncRoot
If Not IPReliability.ContainsKey(IP) Then IPReliability(IP) = 0
IPReliability(IP) = (IPReliability(IP) + Result) / 2
End SyncLock
End Sub
''' <summary>
@@ -418,12 +412,10 @@ RequestFinished:
''' 基于限速,当前可以下载的剩余量。
''' </summary>
Public NetTaskSpeedLimitLeft As Long = -1
Private ReadOnly NetTaskSpeedLimitLeftLock As New Object
''' <summary>
''' 正在运行中的线程数。
''' </summary>
Public NetTaskThreadCount As Integer = 0
Private ReadOnly NetTaskThreadCountLock As New Object
''' <summary>
''' 下载源。
@@ -760,7 +752,6 @@ RequestFinished:
''' 文件的已下载大小。
''' </summary>
Public DownloadDone As Long = 0
Private ReadOnly LockDone As New Object
''' <summary>
''' 文件的校验规则。
''' </summary>
@@ -840,14 +831,12 @@ RequestFinished:
''' </summary>
Private ReadOnly Property ConnectAverage As Integer
Get
SyncLock LockCount
Return If(ConnectCount = 0, -1, ConnectTime / ConnectCount)
End SyncLock
Dim CurrentConnectCount As Integer = ConnectCount '防止竞态条件
Return If(CurrentConnectCount = 0, -1, ConnectTime / CurrentConnectCount)
End Get
End Property
Private Const FilePieceLimit As Long = 256 * 1024
Public ReadOnly LockCount As New Object
Public ReadOnly LockState As New Object
Public ReadOnly LockChain As New Object
Public ReadOnly LockSource As New Object
@@ -929,7 +918,7 @@ StartSingleThreadDownload:
If Source Is Nothing Then Return Nothing
Dim TargetUrl As String = Source.Url
If TargetUrl.Contains("pcl2-server") OrElse TargetUrl.Contains("bmclapi") OrElse TargetUrl.Contains("github.com") OrElse
TargetUrl.Contains("optifine.net") OrElse TargetUrl.Contains("modrinth") OrElse TargetUrl.Contains("momot.rs") Then Return Nothing
TargetUrl.Contains("optifine.net") OrElse TargetUrl.Contains("momot.rs") Then Return Nothing
'寻找最大碎片
'FUTURE: 下载引擎重做,计算下载源平均链接时间和线程下载速度,按最高时间节省来开启多线程
Dim FilePieceMax As NetThread = Threads
@@ -962,9 +951,7 @@ StartThread:
End SyncLock
'开始线程
SyncLock NetTaskThreadCountLock
NetTaskThreadCount += 1
End SyncLock
Interlocked.Increment(NetTaskThreadCount)
SyncLock LockSource
If Not HasAvailableSource(False) Then SourcesOnce.First.SingleThread = ThreadInfo
End SyncLock
@@ -986,7 +973,7 @@ StartThread:
CancelToken As CancellationTokenSource = Nothing, HostIp As String = Nothing
'部分下载源真的特别慢,并且只需要一个请求,例如 Ping 为 20s,如果增长太慢,就会造成类似 2.5s 5s 7.5s 10s 12.5s... 的极大延迟
'延迟过长会导致某些特别慢的链接迟迟不被掐死
Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 10000) * (1 + Th.Source.FailCount), 30000)
Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 15000) * (1 + Th.Source.FailCount), 30000)
Th.State = NetState.Connecting
Try
Dim HttpDataCount As Integer = 0
@@ -994,8 +981,8 @@ StartThread:
HttpRequest = New HttpRequestMessage(HttpMethod.Get, Th.Source.Url)
SecretHeadersSign(Th.Source.Url, HttpRequest, SimulateBrowserHeaders)
CancelToken = New CancellationTokenSource(Timeout)
HostIp = DNSLookup(HttpRequest, CancelToken) 'DNS 预解析
If Not Th.IsFirstThread Then HttpRequest.Headers.Range = New RangeHeaderValue(Th.DownloadStart, Nothing)
HostIp = DNSLookup(HttpRequest, CancelToken.Token) 'DNS 预解析
If Not Th.IsFirstThread Then HttpRequest.Headers.Range = New Headers.RangeHeaderValue(Th.DownloadStart, Nothing)
Dim ContentLength As Long = 0
Response = ThreadClient.SendAsync(HttpRequest, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
If Not Response.IsSuccessStatusCode Then Throw New Exception($"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Th.Source.Url}") '状态码检查
@@ -1032,7 +1019,7 @@ StartThread:
Dim DriveName As String = Drive.Name.First.ToString
Dim RequiredSpace = If(PathTemp.StartsWithF(DriveName), ContentLength * 1.1, 0) +
If(LocalPath.StartsWithF(DriveName), ContentLength + 5 * 1024 * 1024, 0)
If Drive.TotalFreeSpace < RequiredSpace Then
If RequiredSpace > 0 AndAlso Drive.TotalFreeSpace < RequiredSpace Then
Throw New Exception(DriveName & " 盘空间不足,无法进行下载。" & vbCrLf & "需要至少 " & GetString(RequiredSpace) & " 空间,但当前仅剩余 " & GetString(Drive.TotalFreeSpace) & "。" &
If(PathTemp.StartsWithF(DriveName), vbCrLf & vbCrLf & "下载时需要与文件同等大小的空间存放缓存,你可以在设置中调整缓存文件夹的位置。", ""))
End If
@@ -1040,7 +1027,7 @@ StartThread:
End If
ElseIf FileSize < 0 Then
Throw New Exception("非首线程运行时,尚未获取文件大小")
ElseIf Th.DownloadStart > 0 AndAlso ContentLength = FileSize Then
ElseIf Th.DownloadStart > 0 AndAlso ContentLength >= FileSize Then
NotSupportRange:
SyncLock LockSource
If SourcesOnce.Contains(Th.Source) Then GoTo SourceBreak
@@ -1076,11 +1063,9 @@ NotSupportRange:
Threading.Thread.Sleep(16)
End While
Dim RealDataCount As Integer = If(IsUnknownSize, HttpDataCount, Math.Min(HttpDataCount, Th.DownloadUndone))
SyncLock NetTaskSpeedLimitLeftLock
If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount
End SyncLock
If NetTaskSpeedLimitHigh > 0 Then Interlocked.Add(NetTaskSpeedLimitLeft, -RealDataCount)
Dim DeltaTime = GetTimeMs() - Th.LastReceiveTime
If DeltaTime > 1000000 Then DeltaTime = 1 '避免时间刻反转导致出现极大值
If DeltaTime > 100000000 Then DeltaTime = 1 '避免时间刻反转导致出现极大值
If RealDataCount > 0 Then
'有数据
If Th.DownloadDone = 0 Then
@@ -1089,27 +1074,21 @@ NotSupportRange:
SyncLock LockState
If State < NetState.Downloading Then State = NetState.Downloading
End SyncLock
SyncLock LockCount
ConnectCount += 1
ConnectTime += GetTimeMs() - Th.InitTime
End SyncLock
Interlocked.Increment(ConnectCount)
Interlocked.Add(ConnectTime, GetTimeMs() - Th.InitTime)
End If
SyncLock LockCount
Th.Source.FailCount = 0
For Each Task In Tasks
Task.FailCount = 0
Next
End SyncLock
NetManager.DownloadDone += RealDataCount
SyncLock LockDone
DownloadDone += RealDataCount
End SyncLock
Th.Source.FailCount = 0
For Each Task In Tasks
Task.FailCount = 0
Next
Interlocked.Add(NetManager.DownloadDone, RealDataCount)
Interlocked.Add(DownloadDone, RealDataCount)
Th.DownloadDone += RealDataCount
ResultStream.Write(ResponseBytes, 0, RealDataCount)
'已完成
If Th.DownloadUndone = 0 AndAlso Not IsUnknownSize Then Exit While
'检查速度是否过慢
If DeltaTime > 5000 AndAlso DeltaTime > RealDataCount AndAlso '数据包间隔大于 5s,且速度小于 1K/s
If Th.LastReceiveTime > 0 AndAlso DeltaTime > 5000 AndAlso DeltaTime > RealDataCount AndAlso '数据包间隔大于 5s,且速度小于 1K/s
Th.Source.SingleThread Is Nothing Then '且并非单线程下载
Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。")
End If
@@ -1147,9 +1126,7 @@ SourceBreak:
CancelToken?.Dispose()
If Not IsNoSplit Then ResultStream?.Dispose()
'改变计数
SyncLock NetTaskThreadCountLock
NetTaskThreadCount -= 1
End SyncLock
Interlocked.Decrement(NetTaskThreadCount)
'合并
If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso
State < NetState.Merging AndAlso Th.State <> NetState.Interrupted Then Merge(Th)
@@ -1157,12 +1134,10 @@ SourceBreak:
End Sub
Private Sub SourceFail(Th As NetThread, ex As Exception, IsMergeFailure As Boolean)
'状态变更
SyncLock LockCount
Th.Source.FailCount += 1
For Each Task In Tasks
Task.FailCount += 1
Next
End SyncLock
Interlocked.Increment(Th.Source.FailCount)
For Each Task In Tasks
Interlocked.Increment(Task.FailCount)
Next
Th.State = NetState.Interrupted
Th.Source.Ex = ex
'根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传)
@@ -1241,10 +1216,8 @@ SourceBreak:
SyncLock LockChain
Threads = Nothing
End SyncLock
NetManager.DownloadDone -= DownloadDone
SyncLock LockDone
DownloadDone = 0
End SyncLock
Interlocked.Add(NetManager.DownloadDone, -DownloadDone)
DownloadDone = 0
SpeedLastDone = 0
End Sub
@@ -1271,7 +1244,7 @@ Retry:
SyncLock LockChain
'合并文件
If IsNoSplit Then
'仅有一个线程,从缓存中输出
'仅有一个线程,从内存中输出
If ModeDebug Then Log($"[Download] {LocalName}:下载结束,从内存输出文件")
Cache.Position = 0
WriteFile(LocalPath, Cache)
@@ -1321,6 +1294,7 @@ Retry:
End SyncLock
Catch ex As Exception
RetryCount += 1
If State > NetState.Merging Then Return '通常是因为在合并时被中断了
Log(ex, $"合并文件出错,第 {RetryCount} 次尝试({LocalName})")
'重新尝试合并
If RetryCount < 3 Then
@@ -1372,9 +1346,7 @@ Retry:
Private Sub AbortInternal()
On Error Resume Next
Reset()
SyncLock NetManager.LockRemain
NetManager.FileRemain -= 1
End SyncLock
Interlocked.Decrement(NetManager.FileRemain)
End Sub
'状态改变接口
@@ -1386,9 +1358,7 @@ Retry:
If State >= NetState.Finished Then Return
State = NetState.Finished
End SyncLock
SyncLock NetManager.LockRemain
NetManager.FileRemain -= 1
End SyncLock
Interlocked.Decrement(NetManager.FileRemain)
If PrintLog Then Log($"[Download] {LocalName}:已完成")
For Each Task In Tasks
Task.OnFileFinish(Me)
@@ -1522,8 +1492,8 @@ FinishExCatch:
If File.LocalPath.EndsWithF("\") Then Throw New ArgumentException("请输入含文件名的完整文件路径:" & File.LocalPath)
Directory.CreateDirectory(GetPathFromFullPath(File.LocalPath)) '创建目标文件夹
Next
'接入下载管理器
NetManager.Start(Me)
'接入下载管理器,获取新开始下载的文件列表
Dim NewFiles = NetManager.Start(Me)
'====================================
' 已存在文件查找
@@ -1531,9 +1501,8 @@ FinishExCatch:
'整理允许进行查找的文件
Dim FilesToCheck As New List(Of NetFile)
Dim DisabledCopy As Boolean = Setup.Get("SystemDebugSkipCopy") '在设置中禁用了复制
For Each File In Files
If Not DisabledCopy AndAlso File.Check?.CanUseExistsFile Then
For Each File In NewFiles
If File.Check?.CanUseExistsFile Then
FilesToCheck.Add(File)
Else '不允许,直接开始下载
SyncLock LockState
@@ -1570,7 +1539,7 @@ FinishExCatch:
End Sub
Private Sub CheckExistingFiles(Files As List(Of NetFile), FolderList As List(Of String))
Try
If ModeDebug Then Log($"[Download] 文件检查线程已启动,分配的文件数:{Files.Count}")
If ModeDebug Then Log($"[Download] 文件检查开始,本线程负责的文件数:{Files.Count}")
'列出 MC 文件夹中的各个版本文件夹
Dim VersionFolders As New List(Of String)
For Each McFolder In FolderList
@@ -1584,7 +1553,7 @@ FinishExCatch:
'处理每个文件
For Each File As NetFile In Files
Dim Target As String = CheckExistingFile(FolderList, VersionFolders, File)
If File.State >= NetState.WaitingToDownload Then Return '中断
If File.State >= NetState.WaitingToDownload Then Continue For '中断
'已找到相同文件
If Target IsNot Nothing Then
File.IsCopy = True
@@ -1594,6 +1563,8 @@ Retry:
If Target <> File.LocalPath Then
Log($"[Download] 复制已存在的文件:{Target} → {File.LocalPath}")
CopyFile(Target, File.LocalPath)
Else
If ModeDebug Then Log($"[Download] 需要下载的文件已存在,并通过校验:{Target}")
End If
File.Finish(False)
Continue For
@@ -1619,6 +1590,8 @@ Retry:
Private Function CheckExistingFile(FolderList As List(Of String), VersionFolders As List(Of String), File As NetFile) As String
'目标文件已存在
If File.Check.Check(File.LocalPath) Is Nothing Then Return File.LocalPath
'在设置中禁用了复制
If Setup.Get("SystemDebugSkipCopy") Then Return Nothing
'没有可用的检查规则,只能开始下载
If File.Check.Hash Is Nothing AndAlso File.Check.ActualSize < 0 Then Return Nothing
'大致判断文件类别
@@ -1633,7 +1606,7 @@ Retry:
Return Nothing
End If
End If
Dim Type = TypeIndexes.MaxOf(Function(kv) kv.Item2).FolderName.TrimStart("\"c)
Dim Type = TypeIndexes.MaxBy(Function(kv) kv.Item2).FolderName.TrimStart("\"c)
'根据类别进行查找
Static Sizes As New SafeDictionary(Of String, Long)
Select Case Type
@@ -1757,8 +1730,7 @@ Retry:
''' <summary>
''' 需要下载的文件。为“本地地址 - 文件对象”键值对。
''' </summary>
Public Files As New Dictionary(Of String, NetFile)
Public ReadOnly LockFiles As New Object
Public AllFiles As New SafeDictionary(Of String, NetFile)
''' <summary>
''' 当前的所有下载任务。
@@ -1768,25 +1740,12 @@ Retry:
''' <summary>
''' 已下载完成的大小。
''' </summary>
Public Property DownloadDone As Long
Get
Return _DownloadDone
End Get
Set(value As Long)
SyncLock LockDone
_DownloadDone = value
End SyncLock
End Set
End Property
Private _DownloadDone As Long = 0
Private ReadOnly LockDone As New Object
Public DownloadDone As Long = 0
''' <summary>
''' 尚未完成下载的文件数。
''' </summary>
Public FileRemain As Integer = 0
Public ReadOnly LockRemain As New Object
'这些属性由 RefreshStat 刷新
''' <summary>
@@ -1855,9 +1814,9 @@ Retry:
Thread.Sleep(20)
'获取文件列表
Dim AllFiles As List(Of NetFile)
SyncLock LockFiles
If Id = 0 AndAlso FileRemain = 0 AndAlso Files.Any() Then Files.Clear() '若已完成,则清空
AllFiles = Files.Values.ToList()
SyncLock Me.AllFiles.SyncRoot
If Id = 0 AndAlso FileRemain = 0 AndAlso Me.AllFiles.Any() Then Me.AllFiles.Clear() '若已完成,则清空
AllFiles = Me.AllFiles.Values.ToList()
End SyncLock
Dim WaitingFiles As New List(Of NetFile)
Dim OngoingFiles As New List(Of NetFile)
@@ -1865,7 +1824,7 @@ Retry:
If File.Uuid Mod 2 = Id Then Continue For
If File.State = NetState.WaitingToDownload Then
WaitingFiles.Add(File)
ElseIf File.State < NetState.Merging Then
ElseIf File.State < NetState.Merging AndAlso File.State >= NetState.WaitingToDownload Then
OngoingFiles.Add(File)
End If
Next
@@ -1908,7 +1867,7 @@ Retry:
Dim NextTick As Long = GetTimeMs()
While True
'增加限速余量
If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft = NetTaskSpeedLimitHigh / 10
If NetTaskSpeedLimitHigh > 0 Then Interlocked.Add(NetTaskSpeedLimitLeft, CLng(NetTaskSpeedLimitHigh / 10))
'刷新公开属性
RefreshStat()
'等待 100 ms
@@ -1931,7 +1890,7 @@ Retry:
''' <summary>
''' 开始一个下载任务。
''' </summary>
Public Sub Start(Task As LoaderDownload)
Public Function Start(Task As LoaderDownload) As List(Of NetFile)
StartManager()
'清理缓存
SyncLock DownloadCacheLock '防止同时开启多个下载任务时重复清理
@@ -1943,45 +1902,30 @@ Retry:
Catch ex As Exception
Log(ex, "清理下载缓存失败")
End Try
IsDownloadCacheCleared = True
End If
IsDownloadCacheCleared = True
End SyncLock
'文件处理
SyncLock LockFiles
'添加每个文件
For i = 0 To Task.Files.Count - 1
Dim File = Task.Files(i)
If Files.ContainsKey(File.LocalPath) Then
'已有该文件
If Files(File.LocalPath).State >= NetState.Finished Then
'该文件已经下载过一次,且下载完成
'将已下载的文件替换成当前文件,重新下载
File.Tasks.Add(Task)
Files(File.LocalPath) = File
SyncLock LockRemain
FileRemain += 1
End SyncLock
If ModeDebug Then Log($"[Download] {File.LocalName}:重新加入下载列表,目标 {File.LocalPath}")
Else
'该文件正在下载中
'将当前文件替换成下载中的文件,即两个任务指向同一个文件
File = Files(File.LocalPath)
File.Tasks.Add(Task)
End If
Else
'没有该文件
File.Tasks.Add(Task)
Files.Add(File.LocalPath, File)
SyncLock LockRemain
FileRemain += 1
End SyncLock
If ModeDebug Then Log($"[Download] {File.LocalName}:已加入下载列表,目标 {File.LocalPath}")
End If
Task.Files(i) = File '回设
Next
End SyncLock
'添加每个文件
Dim NewFiles As New List(Of NetFile)
For i = 0 To Task.Files.Count - 1
Dim File As NetFile = Task.Files(i)
If AllFiles.ContainsKey(File.LocalPath) AndAlso AllFiles(File.LocalPath).State < NetState.Finished Then
'该文件正在被另一个任务下载
If ModeDebug Then Log($"[Download] {File.LocalName}:无需开始,该文件已在下载中,目标 {File.LocalPath}")
File = AllFiles(File.LocalPath) '将列表中的文件替换成下载中的文件,即两个任务指向同一个文件;抛弃现在的 File 对象
Task.Files(i) = File
Else
'常规情况(该文件可能已在其他任务中下载完成,但这并不影响)
If ModeDebug Then Log($"[Download] {File.LocalName}:开始,目标 {File.LocalPath}")
AllFiles(File.LocalPath) = File
NewFiles.Add(File)
Interlocked.Increment(FileRemain)
End If
File.Tasks.Add(Task)
Next
Tasks.Add(Task)
End Sub
Return NewFiles
End Function
End Class