2.12.4
This commit is contained in:
@@ -8,7 +8,12 @@ Public Module ModAnimation
|
||||
''' <summary>
|
||||
''' 动画速度。最大为 200。
|
||||
''' </summary>
|
||||
Public AniSpeed As Double = 1
|
||||
Public ReadOnly Property AniSpeed As Double
|
||||
Get
|
||||
Dim Setting = PCL.Settings.Get("SystemDebugAnim")
|
||||
Return If(Setting >= 30, 200, MathClamp(Setting * 0.1 + 0.1, 0.1, 3))
|
||||
End Get
|
||||
End Property
|
||||
''' <summary>
|
||||
''' 动画组列表。
|
||||
''' </summary>
|
||||
|
||||
@@ -14,13 +14,13 @@ Public Module ModBase
|
||||
#Region "声明"
|
||||
|
||||
'下列版本信息由更新器自动修改
|
||||
Public Const VersionBaseName As String = "2.12.3" '不含分支前缀的显示用版本名
|
||||
Public Const VersionStandardCode As String = "2.12.3." & VersionBranchCode '标准格式的四段式版本号
|
||||
Public Const VersionBaseName As String = "2.12.4" '不含分支前缀的显示用版本名
|
||||
Public Const VersionStandardCode As String = "2.12.4." & VersionBranchCode '标准格式的四段式版本号
|
||||
Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
|
||||
#If BETA Then
|
||||
Public Const VersionCode As Integer = 381 'Release
|
||||
Public Const VersionCode As Integer = 383 'Release
|
||||
#Else
|
||||
Public Const VersionCode As Integer = 382 'Snapshot
|
||||
Public Const VersionCode As Integer = 384 'Snapshot
|
||||
#End If
|
||||
'自动生成的版本信息
|
||||
Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
|
||||
@@ -56,10 +56,6 @@ Public Module ModBase
|
||||
''' </summary>
|
||||
Public Lang As String = "zh_CN"
|
||||
''' <summary>
|
||||
''' 设置对象。
|
||||
''' </summary>
|
||||
Public Setup As New ModSetup
|
||||
''' <summary>
|
||||
''' 程序的打开计时。
|
||||
''' </summary>
|
||||
Public ApplicationStartTick As Long = GetTimeMs()
|
||||
@@ -86,11 +82,11 @@ Public Module ModBase
|
||||
''' <summary>
|
||||
''' 系统盘盘符,以 \ 结尾。例如 “C:\”。
|
||||
''' </summary>
|
||||
Public OsDrive As String = Environment.GetLogicalDrives().Where(Function(p) Directory.Exists(p)).First.ToUpper.First & ":\" '#3799
|
||||
Public OsDrive As String = Environment.GetLogicalDrives().Where(Function(p) Directory.Exists(p)).First.Upper.First & ":\" '#3799
|
||||
''' <summary>
|
||||
''' 程序的缓存文件夹路径,以 \ 结尾。
|
||||
''' </summary>
|
||||
Public PathTemp As String = If(Setup.Get("SystemSystemCache") = "", IO.Path.GetTempPath() & "PCL\", Setup.Get("SystemSystemCache")).ToString.Replace("/", "\").TrimEnd("\") & "\"
|
||||
Public PathTemp As String = If(Settings.Get("SystemSystemCache") = "", IO.Path.GetTempPath() & "PCL\", Settings.Get("SystemSystemCache")).ToString.Replace("/", "\").TrimEnd("\") & "\"
|
||||
''' <summary>
|
||||
''' AppData 中的 PCL 文件夹路径,以 \ 结尾。
|
||||
''' </summary>
|
||||
@@ -416,17 +412,13 @@ Public Module ModBase
|
||||
Loading
|
||||
Finished
|
||||
Failed
|
||||
Aborted
|
||||
Interrupted
|
||||
End Enum
|
||||
|
||||
''' <summary>
|
||||
''' 执行返回值。
|
||||
''' </summary>
|
||||
Public Enum ProcessReturnValues
|
||||
''' <summary>
|
||||
''' 执行成功,或进程被中断。
|
||||
''' </summary>
|
||||
Aborted = -1
|
||||
''' <summary>
|
||||
''' 执行成功。
|
||||
''' </summary>
|
||||
@@ -620,7 +612,8 @@ Public Module ModBase
|
||||
Public Sub DeleteReg(Key As String, Optional ThrowException As Boolean = False)
|
||||
Try
|
||||
Dim SubKey As Microsoft.Win32.RegistryKey = My.Computer.Registry.CurrentUser.OpenSubKey("Software\" & RegFolder, True)
|
||||
SubKey?.DeleteValue(Key)
|
||||
If SubKey?.GetValue(Key) Is Nothing Then Return
|
||||
SubKey.DeleteValue(Key)
|
||||
Catch ex As Exception
|
||||
Log(ex, "删除注册表出错:" & Key, If(ThrowException, LogLevel.Hint, LogLevel.Developer))
|
||||
If ThrowException Then Throw
|
||||
@@ -1253,11 +1246,11 @@ Re:
|
||||
End If
|
||||
If Not String.IsNullOrEmpty(Hash) Then
|
||||
If Hash.Length < 35 Then 'MD5
|
||||
If Hash.ToLowerInvariant <> GetFileMD5(LocalPath) Then Return "文件 MD5 应为 " & Hash & ",实际为 " & GetFileMD5(LocalPath)
|
||||
If Hash.Lower <> GetFileMD5(LocalPath) Then Return "文件 MD5 应为 " & Hash & ",实际为 " & GetFileMD5(LocalPath)
|
||||
ElseIf Hash.Length = 64 Then 'SHA256
|
||||
If Hash.ToLowerInvariant <> GetFileSHA256(LocalPath) Then Return "文件 SHA256 应为 " & Hash & ",实际为 " & GetFileSHA256(LocalPath)
|
||||
If Hash.Lower <> GetFileSHA256(LocalPath) Then Return "文件 SHA256 应为 " & Hash & ",实际为 " & GetFileSHA256(LocalPath)
|
||||
Else 'SHA1 (40)
|
||||
If Hash.ToLowerInvariant <> GetFileSHA1(LocalPath) Then Return "文件 SHA1 应为 " & Hash & ",实际为 " & GetFileSHA1(LocalPath)
|
||||
If Hash.Lower <> GetFileSHA1(LocalPath) Then Return "文件 SHA1 应为 " & Hash & ",实际为 " & GetFileSHA1(LocalPath)
|
||||
End If
|
||||
End If
|
||||
If IsJson Then
|
||||
@@ -1287,7 +1280,7 @@ Re:
|
||||
'解压 gz(gz 不需要考虑编码)
|
||||
If CompressFilePath.EndsWithF(".gz", True) Then
|
||||
Using stream As New GZipStream(New FileStream(CompressFilePath, FileMode.Open, FileAccess.Read, FileShare.Read), CompressionMode.Decompress)
|
||||
Using decompressedFile As New FileStream(DestDirectory & GetFileNameFromPath(CompressFilePath).ToLower.Replace(".tar", "").Replace(".gz", ""),
|
||||
Using decompressedFile As New FileStream(DestDirectory & GetFileNameFromPath(CompressFilePath).Lower.Replace(".tar", "").Replace(".gz", ""),
|
||||
FileMode.OpenOrCreate, FileAccess.Write)
|
||||
Dim data As Integer = stream.ReadByte()
|
||||
While data <> -1
|
||||
@@ -1332,7 +1325,7 @@ Re:
|
||||
Public Function DeleteDirectory(Path As String, Optional IgnoreIssue As Boolean = False) As Integer
|
||||
If Not Directory.Exists(Path) Then Return 0
|
||||
If Not Path.EndsWithF("\") Then Path &= "\"
|
||||
If Path.EndsWith(":\") OrElse
|
||||
If Path.EndsWithF(":\") OrElse
|
||||
Path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) & "\" OrElse
|
||||
Path = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) & "\" OrElse
|
||||
Path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) & "\" Then '#7030
|
||||
@@ -1416,17 +1409,25 @@ RetryDir:
|
||||
|
||||
''' <summary>
|
||||
''' 若路径长度大于指定值,则将长路径转换为短路径。
|
||||
''' 如果路径不存在,则返回原始路径。
|
||||
''' </summary>
|
||||
Public Function ShortenPath(LongPath As String) As String
|
||||
If LongPath.Length <= 200 OrElse LongPath.StartsWithF("http", True) Then Return LongPath
|
||||
If Not Directory.Exists(LongPath) AndAlso Not File.Exists(LongPath) Then Return LongPath
|
||||
Dim ShortPath As New StringBuilder(260)
|
||||
GetShortPathName(LongPath.Replace("/", "\"), ShortPath, 260) '第一个参数也会在调用后被修改,使用 Replace 后不会影响原始字符串
|
||||
ShortenPath = ShortPath.ToString
|
||||
If String.IsNullOrEmpty(ShortenPath) Then Return LongPath
|
||||
Public Function ShortenPath(Raw As String) As String
|
||||
If Raw.Length <= 200 OrElse Raw.StartsWithF("http", True) Then Return Raw
|
||||
If Not Directory.Exists(Raw) AndAlso Not File.Exists(Raw) Then Return Raw
|
||||
Dim PathToShorten As String, Ignored As String
|
||||
If Not Directory.Exists(Raw) AndAlso Raw.EndsWithF(".jar", True) Then '只能缩短文件夹部分,必须保留 jar 文件名,否则会导致 Forge 1.20.1 无法通过文件名识别模块名
|
||||
PathToShorten = GetPathFromFullPath(Raw)
|
||||
Ignored = GetFileNameFromPath(Raw)
|
||||
Else
|
||||
PathToShorten = Raw
|
||||
Ignored = ""
|
||||
End If
|
||||
Dim ShortPathBuffer As New StringBuilder(260)
|
||||
GetShortPathNameW(PathToShorten.Replace("/", "\"), ShortPathBuffer, 260)
|
||||
Dim DirShortPath As String = ShortPathBuffer.ToString()
|
||||
If String.IsNullOrEmpty(DirShortPath) Then Return Raw
|
||||
Return If(String.IsNullOrEmpty(Ignored), DirShortPath, IO.Path.Combine(DirShortPath, Ignored))
|
||||
End Function
|
||||
Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" (lpszLongPath As String, lpszShortPath As StringBuilder, cchBuffer As Integer) As Integer
|
||||
Private Declare Unicode Function GetShortPathNameW Lib "kernel32" Alias "GetShortPathNameW" (lpszLongPath As String, lpszShortPath As StringBuilder, cchBuffer As Integer) As Integer
|
||||
|
||||
#End Region
|
||||
|
||||
@@ -1493,7 +1494,7 @@ RetryDir:
|
||||
Ex = Ex.InnerException
|
||||
Loop
|
||||
DescList = DescList.Distinct.ToList
|
||||
Dim Desc As String = Join(DescList, vbCrLf & "→ ")
|
||||
Dim Desc As String = DescList.Join(vbCrLf & "→ ")
|
||||
|
||||
'构造输出信息
|
||||
Dim UsualReason As String = AnalyzeUsualReason(InnerEx, OuterEx, DescList)
|
||||
@@ -1501,7 +1502,7 @@ RetryDir:
|
||||
Return UsualReason & "详细错误:" & DescList.First
|
||||
Else
|
||||
DescList.Reverse() '让最深层错误在最左边
|
||||
Return Join(DescList, " ← ")
|
||||
Return DescList.Join(" ← ")
|
||||
End If
|
||||
End Function
|
||||
Private Function AnalyzeUsualReason(InnerEx As Exception, OuterEx As Exception, DescList As List(Of String)) As String
|
||||
@@ -1584,7 +1585,7 @@ RetryDir:
|
||||
''' </summary>
|
||||
<Extension> Public Function Capitalize(word As String) As String
|
||||
If String.IsNullOrEmpty(word) Then Return word
|
||||
Return word.Substring(0, 1).ToUpperInvariant() & word.Substring(1).ToLowerInvariant()
|
||||
Return word.Substring(0, 1).Upper() & word.Substring(1).Lower()
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
@@ -1604,7 +1605,24 @@ RetryDir:
|
||||
''' <summary>
|
||||
''' 连接字符串。
|
||||
''' </summary>
|
||||
<Extension> Public Function Join(List As IEnumerable, Split As Char) As String
|
||||
Dim Builder As New StringBuilder
|
||||
Dim IsFirst As Boolean = True
|
||||
For Each Element In List
|
||||
If IsFirst Then
|
||||
IsFirst = False
|
||||
Else
|
||||
Builder.Append(Split)
|
||||
End If
|
||||
If Element IsNot Nothing Then Builder.Append(Element)
|
||||
Next
|
||||
Return Builder.ToString
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 连接字符串。
|
||||
''' </summary>
|
||||
<Extension> Public Function Join(List As IEnumerable, Split As String) As String
|
||||
If Split.IsSingle Then Return List.Join(Split(0))
|
||||
Dim Builder As New StringBuilder
|
||||
Dim IsFirst As Boolean = True
|
||||
For Each Element In List
|
||||
@@ -1622,7 +1640,7 @@ RetryDir:
|
||||
''' 若原始字符串为空,则返回 {""}。
|
||||
''' </summary>
|
||||
<Extension> Public Function Split(FullStr As String, SplitStr As String) As String()
|
||||
If SplitStr.Length = 1 Then
|
||||
If SplitStr.IsSingle Then
|
||||
Return FullStr.Split(SplitStr(0))
|
||||
Else
|
||||
Return FullStr.Split({SplitStr}, StringSplitOptions.None)
|
||||
@@ -1795,6 +1813,19 @@ RetryDir:
|
||||
End Try
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 等效 ToLowerInvariant。
|
||||
''' </summary>
|
||||
<Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)> Public Function Lower(Str As String) As String
|
||||
Return Str.ToLowerInvariant()
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 等效 ToUpperInvariant。
|
||||
''' </summary>
|
||||
<Extension> <MethodImpl(MethodImplOptions.AggressiveInlining)> Public Function Upper(Str As String) As String
|
||||
Return Str.ToUpperInvariant()
|
||||
End Function
|
||||
|
||||
'转义
|
||||
''' <summary>
|
||||
''' 为字符串进行 XML 转义。
|
||||
@@ -1887,8 +1918,8 @@ RetryDir:
|
||||
If String.IsNullOrEmpty(Source) OrElse String.IsNullOrEmpty(Query) Then Return 0
|
||||
Dim qp As Integer = 0, lenSum As Double = 0
|
||||
Dim str As New StringBuilder(Source.Length)
|
||||
str.Append(Source.ToLower().Replace(" ", ""))
|
||||
Query = Query.ToLower().Replace(" ", "")
|
||||
str.Append(Source.Lower().Replace(" ", ""))
|
||||
Query = Query.Lower().Replace(" ", "")
|
||||
Dim sourceLength As Integer = str.Length, queryLength As Integer = Query.Length '用于计算最后因数的长度缓存
|
||||
If queryLength = 0 Then Return 0
|
||||
Do While qp < queryLength
|
||||
@@ -1977,6 +2008,7 @@ RetryDir:
|
||||
''' <summary>
|
||||
''' 进行多段文本加权搜索,获取相似度较高的数项结果。
|
||||
''' 在搜索时,会对每个文本源单独加权,但单个文本源内的多个别名只取最高的一个的相似度。
|
||||
''' 这会修改 Entries 中每项的 Similarity 与 AbsoluteRight 字段。
|
||||
''' </summary>
|
||||
''' <param name="MaxBlurCount">返回的最大模糊结果数。</param>
|
||||
''' <param name="MinBlurSimilarity">返回结果要求的最低相似度。</param>
|
||||
@@ -1997,14 +2029,7 @@ RetryDir:
|
||||
If Entry.AbsoluteRight OrElse Entry.Similarity >= MinBlurSimilarity Then Candidates.Add(Entry)
|
||||
Next
|
||||
'按照相似度进行排序
|
||||
Candidates = Candidates.SortByComparison(
|
||||
Function(Left, Right) As Boolean
|
||||
If Left.AbsoluteRight Xor Right.AbsoluteRight Then
|
||||
Return Left.AbsoluteRight
|
||||
Else
|
||||
Return Left.Similarity > Right.Similarity
|
||||
End If
|
||||
End Function)
|
||||
Candidates = Candidates.SortByComparison(Function(Left, Right) If(Left.AbsoluteRight <> Right.AbsoluteRight, Left.AbsoluteRight, Left.Similarity > Right.Similarity))
|
||||
'返回结果
|
||||
Dim BlurCount As Integer = 0
|
||||
For Each Entry In Candidates
|
||||
@@ -2319,6 +2344,22 @@ NextElement:
|
||||
Return Arr?.Count > 0
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 判断这是否是仅有一个元素的集合。
|
||||
''' </summary>
|
||||
<Extension> Public Function IsSingle(Of T)(Source As IEnumerable(Of T)) As Boolean
|
||||
If Source Is Nothing Then
|
||||
Return False
|
||||
ElseIf TypeOf Source Is IList(Of T) Then
|
||||
Return DirectCast(Source, IList(Of T)).Count = 1
|
||||
Else
|
||||
Using Enumerator = Source.GetEnumerator()
|
||||
If Not Enumerator.MoveNext() Then Return False
|
||||
If Enumerator.MoveNext() Then Return False
|
||||
Return True
|
||||
End Using
|
||||
End If
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 对集合的每个元素执行指定操作。
|
||||
''' </summary>
|
||||
<Extension> Public Function ForEach(Of T)(Collection As IEnumerable(Of T), Action As Action(Of T)) As IEnumerable(Of T)
|
||||
@@ -2548,6 +2589,58 @@ NextElement:
|
||||
End If
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' 选择所有最大值对应的对象。
|
||||
''' 若没有元素则返回空列表。
|
||||
''' </summary>
|
||||
<Extension> Public Function MaxByAll(Of T, C As IComparable(Of C))(Source As IEnumerable(Of T), Selector As Func(Of T, C)) As List(Of T)
|
||||
Dim Results As New List(Of T)
|
||||
Using Enumerator = Source.GetEnumerator()
|
||||
If Not Enumerator.MoveNext() Then Return Results
|
||||
Dim MaxItem As T = Enumerator.Current
|
||||
Dim MaxValue As C = Selector(MaxItem)
|
||||
Results.Add(MaxItem)
|
||||
While Enumerator.MoveNext()
|
||||
Dim CurrentItem = Enumerator.Current
|
||||
Dim CurrentValue = Selector(CurrentItem)
|
||||
Dim Comparison = CurrentValue.CompareTo(MaxValue)
|
||||
If Comparison > 0 Then
|
||||
MaxValue = CurrentValue
|
||||
Results.Clear()
|
||||
Results.Add(CurrentItem)
|
||||
ElseIf Comparison = 0 Then
|
||||
Results.Add(CurrentItem)
|
||||
End If
|
||||
End While
|
||||
End Using
|
||||
Return Results
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 选择所有最小值对应的对象。
|
||||
''' 若没有元素则返回空列表。
|
||||
''' </summary>
|
||||
<Extension> Public Function MinByAll(Of T, C As IComparable(Of C))(List As IEnumerable(Of T), Selector As Func(Of T, C)) As List(Of T)
|
||||
Dim Results As New List(Of T)
|
||||
Using Enumerator = List.GetEnumerator()
|
||||
If Not Enumerator.MoveNext() Then Return Results
|
||||
Dim MinItem As T = Enumerator.Current
|
||||
Dim MinValue As C = Selector(MinItem)
|
||||
Results.Add(MinItem)
|
||||
While Enumerator.MoveNext()
|
||||
Dim CurrentItem = Enumerator.Current
|
||||
Dim CurrentValue = Selector(CurrentItem)
|
||||
Dim Comparison = CurrentValue.CompareTo(MinValue)
|
||||
If Comparison < 0 Then
|
||||
MinValue = CurrentValue
|
||||
Results.Clear()
|
||||
Results.Add(CurrentItem)
|
||||
ElseIf Comparison = 0 Then
|
||||
Results.Add(CurrentItem)
|
||||
End If
|
||||
End While
|
||||
End Using
|
||||
Return Results
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 选择最大值对应的对象。
|
||||
''' 若没有元素则返回 Nothing。
|
||||
@@ -2683,7 +2776,7 @@ NextElement:
|
||||
Try
|
||||
Location = Location.Replace("/", "\").Trim(" "c, """"c)
|
||||
Log("[System] 正在打开资源管理器:" & Location)
|
||||
If Location.EndsWith("\") Then
|
||||
If Location.EndsWithF("\") Then
|
||||
StartProcess(Location)
|
||||
Else
|
||||
StartProcess("explorer", $"/select,""{Location}""")
|
||||
@@ -2883,7 +2976,7 @@ Retry:
|
||||
'类型检查
|
||||
Using Reader As New XamlXmlReader(Stream)
|
||||
While Reader.Read()
|
||||
For Each BlackListType In {GetType(WebBrowser), GetType(Frame), GetType(MediaElement), GetType(ObjectDataProvider), GetType(XamlReader), GetType(Window), GetType(XmlDataProvider)}
|
||||
For Each BlackListType In {GetType(WebBrowser), GetType(Frame), GetType(MediaElement), GetType(ObjectDataProvider), GetType(XamlReader), GetType(Window), GetType(XmlDataProvider), GetType(SettingService)}
|
||||
If Reader.Type IsNot Nothing AndAlso BlackListType.IsAssignableFrom(Reader.Type.UnderlyingType) Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListType.Name} 类型。")
|
||||
If Reader.Value IsNot Nothing AndAlso Reader.Value = BlackListType.Name Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListType.Name} 值。")
|
||||
Next
|
||||
@@ -2940,11 +3033,22 @@ Retry:
|
||||
Return formattedText.Width
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 将布尔值转换为 Visibility。True 转换为 Visible,False 转换为 Collapsed。
|
||||
''' </summary>
|
||||
<Extension> Public Function ToVisibility(IsVisible As Boolean) As Visibility
|
||||
Return If(IsVisible, Visibility.Visible, Visibility.Collapsed)
|
||||
End Function
|
||||
|
||||
#End Region
|
||||
|
||||
#Region "Debug"
|
||||
|
||||
Public ModeDebug As Boolean = False
|
||||
Public ReadOnly Property ModeDebug As Boolean
|
||||
Get
|
||||
Return Settings.Get("SystemDebugMode")
|
||||
End Get
|
||||
End Property
|
||||
|
||||
'Log
|
||||
Public Enum LogLevel
|
||||
@@ -3217,7 +3321,7 @@ Retry:
|
||||
|
||||
'遥测
|
||||
Public Sub Telemetry([Event] As String, ParamArray Datas As String())
|
||||
If Not Setup.Get("SystemSystemTelemetry") Then Return '用户关闭了遥测
|
||||
If Not Settings.Get("SystemSystemTelemetry") Then Return '用户关闭了遥测
|
||||
If Not ClsBaseUrl.StartsWithF("http") Then Return '开源版没有设置遥测地址
|
||||
If VersionBranchName = "Debug" Then Return '开发版本不上传遥测
|
||||
RunInNewThread(
|
||||
@@ -3237,10 +3341,10 @@ Retry:
|
||||
|
||||
'获取当前的堆栈信息
|
||||
Public Function GetStackTrace() As String
|
||||
Dim Stack As New StackTrace()
|
||||
Return Join(Stack.GetFrames().Skip(1).Select(Function(f) f.GetMethod).
|
||||
Select(Function(f) f.Name & "(" & Join(f.GetParameters.Select(Function(p) p.ToString).ToList, ", ") & ") - " & f.Module.ToString).ToList,
|
||||
vbCrLf).Replace(vbCrLf & vbCrLf, vbCrLf)
|
||||
Return (New StackTrace).GetFrames().Skip(1).
|
||||
Select(Function(f) f.GetMethod).
|
||||
Select(Function(f) $"{f.Name}({f.GetParameters.Select(Function(p) p.ToString).Join(", ")}) - {f.Module}").
|
||||
Join(vbCrLf).Replace(vbCrLf & vbCrLf, vbCrLf)
|
||||
End Function
|
||||
|
||||
#End Region
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
Set(value As LoadState)
|
||||
If _State = value Then Return
|
||||
Dim OldState = _State
|
||||
If value = LoadState.Finished AndAlso Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000))
|
||||
If value = LoadState.Finished AndAlso Settings.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000))
|
||||
_State = value
|
||||
If Not LogBlackList.Contains(Name) Then Log($"[Loader] 加载器 {Name} 状态改变:{GetStringFromEnum(value)}")
|
||||
'实现 ILoadingTrigger 接口与 OnStateChanged 回调
|
||||
@@ -149,7 +149,7 @@
|
||||
Case LoadState.Waiting
|
||||
Return 0
|
||||
Case LoadState.Loading
|
||||
Return If(_Progress = -1, 0.02, _Progress)
|
||||
Return If(_Progress = -1, 0, _Progress)
|
||||
Case Else
|
||||
Return 1
|
||||
End Select
|
||||
@@ -170,7 +170,7 @@
|
||||
|
||||
'状态变化
|
||||
Public MustOverride Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
|
||||
Public MustOverride Sub Abort()
|
||||
Public MustOverride Sub Interrupt()
|
||||
Public MustOverride Sub Failed(Ex As Exception)
|
||||
|
||||
'等待结束
|
||||
@@ -187,7 +187,7 @@
|
||||
Loop
|
||||
If State = LoadState.Finished Then
|
||||
Return
|
||||
ElseIf State = LoadState.Aborted Then
|
||||
ElseIf State = LoadState.Interrupted Then
|
||||
Throw New ThreadInterruptedException("加载器执行已中断。")
|
||||
ElseIf IsNothing([Error]) Then
|
||||
Throw New Exception("未知错误!")
|
||||
@@ -210,7 +210,7 @@
|
||||
Loop
|
||||
If State = LoadState.Finished Then
|
||||
Return
|
||||
ElseIf State = LoadState.Aborted Then
|
||||
ElseIf State = LoadState.Interrupted Then
|
||||
Throw New ThreadInterruptedException("加载器执行已中断。")
|
||||
ElseIf IsNothing([Error]) Then
|
||||
Throw New Exception("未知错误!")
|
||||
@@ -241,16 +241,16 @@
|
||||
''' <summary>
|
||||
''' 当前执行线程是否应当中断。只应用在加载器的工作线程中判断,不可跨线程调用。
|
||||
''' </summary>
|
||||
Public ReadOnly Property IsAborted As Boolean
|
||||
Public ReadOnly Property IsInterrupted As Boolean
|
||||
Get
|
||||
Return IsAbortedWithThread(Thread.CurrentThread)
|
||||
Return IsInterruptedWithThread(Thread.CurrentThread)
|
||||
End Get
|
||||
End Property
|
||||
''' <summary>
|
||||
''' 当前执行线程是否应当中断。需要手动提供加载器线程,用于需要跨线程检查的情况。
|
||||
''' </summary>
|
||||
Public Function IsAbortedWithThread(Thread As Thread) As Boolean
|
||||
Return LastRunningThread Is Nothing OrElse Not ReferenceEquals(Thread, LastRunningThread) OrElse State = LoadState.Aborted
|
||||
Public Function IsInterruptedWithThread(Thread As Thread) As Boolean
|
||||
Return LastRunningThread Is Nothing OrElse Not ReferenceEquals(Thread, LastRunningThread) OrElse State = LoadState.Interrupted
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 在输入相同时使用原有结果的超时,单位为毫秒。
|
||||
@@ -305,7 +305,7 @@
|
||||
'确认是否开始加载
|
||||
If ShouldStart(Input, IsForceRestart) Then
|
||||
'输入不同或失败,开始加载
|
||||
If State = LoadState.Loading Then TriggerThreadAbort()
|
||||
If State = LoadState.Loading Then TriggerThreadInterrupt()
|
||||
Me.Input = Input
|
||||
SyncLock LockState
|
||||
State = LoadState.Loading
|
||||
@@ -321,7 +321,7 @@
|
||||
IsForceRestarting = IsForceRestart
|
||||
If ModeDebug AndAlso Not LogBlackList.Contains(Name) Then Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已{If(IsForceRestarting, "强制", "")}启动")
|
||||
LoadDelegate(Me)
|
||||
If IsAborted Then
|
||||
If IsInterrupted Then
|
||||
Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已中断但线程正常运行至结束,输出被弃用(最新线程:{If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId)})", LogLevel.Developer)
|
||||
Return
|
||||
End If
|
||||
@@ -331,18 +331,18 @@
|
||||
LastFinishedTime = GetTimeMs() '未中断,本次输出有效
|
||||
Catch ex As CancelledException
|
||||
If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发取消中断,已完成 {Math.Round(Progress * 100)}%")
|
||||
If Not IsAborted Then State = LoadState.Aborted
|
||||
If Not IsInterrupted Then State = LoadState.Interrupted
|
||||
Catch ex As ThreadInterruptedException
|
||||
If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发线程中断,已完成 {Math.Round(Progress * 100)}%")
|
||||
'如果线程是因为判断到 IsAborted 而提前中止,则代表已有新线程被重启,此时不应当改为 Aborted
|
||||
'如果线程是在没有 IsAborted 时手动引发了 ThreadInterruptedException,则代表没有重启线程,这通常代表用户手动取消,应当改为 Aborted
|
||||
If Not IsAborted Then State = LoadState.Aborted
|
||||
'如果线程是因为判断到 IsInterrupted 而提前中止,则代表已有新线程被重启,此时不应当改为 Interrupted
|
||||
'如果线程是在没有 IsInterrupted 时手动引发了 ThreadInterruptedException,则代表没有重启线程,这通常代表用户手动取消,应当改为 Interrupted
|
||||
If Not IsInterrupted Then State = LoadState.Interrupted
|
||||
Catch ex As Exception
|
||||
Failed(ex)
|
||||
End Try
|
||||
End Sub) With {.Name = "L/" & Name, .Priority = ThreadPriority}
|
||||
Try
|
||||
LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsAborted
|
||||
LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsInterrupted
|
||||
Catch ex As ThreadStateException '若遇到偶发的 “线程正在运行或被终止”,则等待后重试
|
||||
Thread.Sleep(500)
|
||||
LastRunningThread.Start()
|
||||
@@ -351,20 +351,20 @@
|
||||
Public Overrides Sub Failed(ex As Exception)
|
||||
[Error] = ex
|
||||
SyncLock LockState
|
||||
If IsAborted OrElse State >= LoadState.Finished Then Return
|
||||
If IsInterrupted OrElse State >= LoadState.Finished Then Return
|
||||
State = LoadState.Failed
|
||||
End SyncLock
|
||||
Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 出错,已完成 {Math.Round(Progress * 100)}%", LogLevel.Developer)
|
||||
TriggerThreadAbort()
|
||||
TriggerThreadInterrupt()
|
||||
End Sub
|
||||
Public Overrides Sub Abort()
|
||||
Public Overrides Sub Interrupt()
|
||||
SyncLock LockState
|
||||
If State <> LoadState.Loading Then Return
|
||||
State = LoadState.Aborted
|
||||
State = LoadState.Interrupted
|
||||
End SyncLock
|
||||
TriggerThreadAbort()
|
||||
TriggerThreadInterrupt()
|
||||
End Sub
|
||||
Private Sub TriggerThreadAbort()
|
||||
Private Sub TriggerThreadInterrupt()
|
||||
If LastRunningThread Is Nothing Then Return
|
||||
If LastRunningThread.IsAlive Then
|
||||
LastRunningThread.Interrupt()
|
||||
@@ -451,11 +451,11 @@
|
||||
End If
|
||||
RunInThread(AddressOf Update)
|
||||
End Sub
|
||||
Public Overrides Sub Abort()
|
||||
Public Overrides Sub Interrupt()
|
||||
'改变状态
|
||||
SyncLock LockState
|
||||
If State = LoadState.Loading OrElse State = LoadState.Waiting Then
|
||||
State = LoadState.Aborted
|
||||
State = LoadState.Interrupted
|
||||
Else
|
||||
Return
|
||||
End If
|
||||
@@ -464,7 +464,7 @@
|
||||
Sub()
|
||||
'中断加载器
|
||||
For Each Loader In Loaders
|
||||
Loader.Abort()
|
||||
Loader.Interrupt()
|
||||
Next
|
||||
End Sub)
|
||||
End Sub
|
||||
@@ -475,7 +475,7 @@
|
||||
State = LoadState.Failed
|
||||
End SyncLock
|
||||
For Each Loader In Loaders
|
||||
Loader.Abort()
|
||||
Loader.Interrupt()
|
||||
Next
|
||||
FrmMain.BtnExtraDownload.ShowRefresh()
|
||||
End Sub
|
||||
@@ -493,9 +493,9 @@
|
||||
Case LoadState.Finished
|
||||
'正常结束,触发刷新
|
||||
Update()
|
||||
Case LoadState.Aborted
|
||||
Case LoadState.Interrupted
|
||||
'被中断,这个任务也中断
|
||||
Abort()
|
||||
Interrupt()
|
||||
Case LoadState.Failed
|
||||
'完蛋,出错了
|
||||
Failed(New Exception(Loader.Name & "失败", Loader.Error))
|
||||
@@ -505,7 +505,7 @@
|
||||
''' 触发一次更新,以启动新加载器或完成。
|
||||
''' </summary>
|
||||
Private Sub Update()
|
||||
If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Aborted Then Return
|
||||
If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Interrupted Then Return
|
||||
Dim IsFinished As Boolean = True
|
||||
Dim Blocked As Boolean = False
|
||||
Dim Input As Object = Me.Input
|
||||
@@ -607,7 +607,7 @@ Restart:
|
||||
'若单个任务已中止,或全部任务已完成,则刷新并移除
|
||||
For Each Task In LoaderTaskbar
|
||||
If LoaderTaskbar.All(Function(l) l.State <> LoadState.Loading) OrElse
|
||||
(Task.State = LoadState.Waiting OrElse Task.State = LoadState.Aborted) Then
|
||||
(Task.State = LoadState.Waiting OrElse Task.State = LoadState.Interrupted) Then
|
||||
FrmSpeedLeft?.TaskRefresh(Task)
|
||||
LoaderTaskbar.Remove(Task)
|
||||
Log($"[Taskbar] {Task.Name} 已移出任务列表")
|
||||
|
||||
@@ -160,7 +160,7 @@ RequestFinished:
|
||||
NetRequestByLoader = ReadFile(Temp)
|
||||
File.Delete(Temp)
|
||||
Finally
|
||||
NewTask.Abort()
|
||||
NewTask.Interrupt()
|
||||
End Try
|
||||
End Function
|
||||
|
||||
@@ -208,7 +208,7 @@ RequestFinished:
|
||||
Catch ex As Exception
|
||||
Throw New WebException($"多线程直接下载文件失败(第一下载源:" & Urls.First() & ")", ex)
|
||||
Finally
|
||||
NewTask.Abort()
|
||||
NewTask.Interrupt()
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
@@ -238,7 +238,7 @@ RequestFinished:
|
||||
End If
|
||||
'写入 Headers
|
||||
For Each Header In If(Headers, New Dictionary(Of String, String))
|
||||
If Header.Key.ToLower = "content-type" Then
|
||||
If Header.Key.Lower = "content-type" Then
|
||||
Request.Content?.Headers.TryAddWithoutValidation(Header.Key, Header.Value)
|
||||
Else
|
||||
Request.Headers.TryAddWithoutValidation(Header.Key, Header.Value)
|
||||
@@ -307,8 +307,10 @@ RequestFinished:
|
||||
''' 返回请求时应使用的 IP。若解析失败,则返回 Nothing。
|
||||
''' </summary>
|
||||
Private Function DNSLookup(Request As HttpRequestMessage, CancelToken As CancellationToken) As String
|
||||
'若一分钟内已失败过,则不再重复尝试解析,以减少断网时的 Exception 数量
|
||||
Dim Host = Request.RequestUri.Host
|
||||
'不对部分有严格 SNI 限制的域名进行 DNS 解析(#8295)
|
||||
If {"mojang.com", "minecraft.net", "minecraftservices.com"}.Any(Function(h) Host.ContainsF(h, True)) Then Return Nothing
|
||||
'若一分钟内已失败过,则不再重复尝试解析,以减少断网时的 Exception 数量
|
||||
SyncLock DNSFailureRecord.SyncRoot
|
||||
If DNSFailureRecord.ContainsKey(Host) Then
|
||||
If Date.Now - DNSFailureRecord(Host) < New TimeSpan(0, 1, 0) Then
|
||||
@@ -399,7 +401,11 @@ RequestFinished:
|
||||
''' <summary>
|
||||
''' 最大线程数。
|
||||
''' </summary>
|
||||
Public NetTaskThreadLimit As Integer
|
||||
Public ReadOnly Property NetTaskThreadLimit As Integer
|
||||
Get
|
||||
Return Settings.Get("ToolDownloadThread") + 1
|
||||
End Get
|
||||
End Property
|
||||
''' <summary>
|
||||
''' 速度下限。
|
||||
''' </summary>
|
||||
@@ -407,7 +413,27 @@ RequestFinished:
|
||||
''' <summary>
|
||||
''' 速度上限。若无限制则为 -1。
|
||||
''' </summary>
|
||||
Public NetTaskSpeedLimitHigh As Long = -1
|
||||
Public ReadOnly Property NetTaskSpeedLimitHigh As Long
|
||||
Get
|
||||
If _NetTaskSpeedLimitHigh Is Nothing Then
|
||||
Dim Setting = PCL.Settings.Get("ToolDownloadSpeed")
|
||||
If Setting <= 14 Then
|
||||
_NetTaskSpeedLimitHigh = (Setting + 1) * 0.1 * 1024 * 1024L
|
||||
ElseIf Setting <= 31 Then
|
||||
_NetTaskSpeedLimitHigh = (Setting - 11) * 0.5 * 1024 * 1024L
|
||||
ElseIf Setting <= 41 Then
|
||||
_NetTaskSpeedLimitHigh = (Setting - 21) * 1024 * 1024L
|
||||
Else
|
||||
_NetTaskSpeedLimitHigh = -1
|
||||
End If
|
||||
End If
|
||||
Return _NetTaskSpeedLimitHigh.Value
|
||||
End Get
|
||||
End Property
|
||||
Private _NetTaskSpeedLimitHigh As Long? = Nothing
|
||||
Public Sub UpdateNetTaskSpeedLimitHigh(Value As Integer)
|
||||
_NetTaskSpeedLimitHigh = Nothing
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 基于限速,当前可以下载的剩余量。
|
||||
''' </summary>
|
||||
@@ -917,7 +943,7 @@ StartSingleThreadDownload:
|
||||
Dim Source As NetSource = GetSource()
|
||||
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
|
||||
If TargetUrl.Contains("pcl2-server") OrElse TargetUrl.Contains("meloong.com") OrElse TargetUrl.Contains("bmclapi") OrElse TargetUrl.Contains("github.com") OrElse
|
||||
TargetUrl.Contains("optifine.net") OrElse TargetUrl.Contains("momot.rs") Then Return Nothing
|
||||
'寻找最大碎片
|
||||
'FUTURE: 下载引擎重做,计算下载源平均链接时间和线程下载速度,按最高时间节省来开启多线程
|
||||
@@ -1053,7 +1079,7 @@ NotSupportRange:
|
||||
End If
|
||||
'开始下载
|
||||
ResponseStream = Response.Content.ReadAsStreamAsync().GetAwaiter().GetResult()
|
||||
If Setup.Get("SystemDebugDelay") Then Threading.Thread.Sleep(RandomInteger(50, 3000))
|
||||
If Settings.Get("SystemDebugDelay") Then Threading.Thread.Sleep(RandomInteger(50, 3000))
|
||||
Dim ResponseBytes As Byte() = New Byte(16384) {}
|
||||
HttpDataCount = ResponseStream.ReadAsync(ResponseBytes, 0, 16384, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
|
||||
While (IsUnknownSize OrElse Th.DownloadUndone > 0) AndAlso '判断是否下载完成
|
||||
@@ -1145,7 +1171,7 @@ SourceBreak:
|
||||
If IsMergeFailure OrElse IsRangeNotSupported OrElse
|
||||
ex.Message.Contains("(502)") OrElse ex.Message.Contains("(404)") OrElse
|
||||
ex.Message.Contains("未能解析") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse
|
||||
((ex.Message.Contains("(403)") OrElse ex.Message.Contains("(429)")) AndAlso Not Th.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403/429,所以不应因此禁用下载源
|
||||
((ex.Message.Contains("(403)") OrElse ex.Message.Contains("(429)")) AndAlso Not Th.Source.Url.Contains("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403/429,所以不应因此禁用下载源
|
||||
(Th.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) AndAlso DownloadDone < 1) OrElse Th.Source.FailCount > NetTaskThreadLimit + 2 Then
|
||||
'当一个下载源有多个线程在下载时,只选择其中一个线程进行后续处理
|
||||
Dim IsThisFail As Boolean = False
|
||||
@@ -1323,7 +1349,7 @@ Retry:
|
||||
Log($"[Download] {LocalName}:已失败,当前状态 {State}")
|
||||
State = NetState.Interrupted
|
||||
End SyncLock
|
||||
AbortInternal()
|
||||
InterruptInternal()
|
||||
For Each Task In Tasks
|
||||
Task.OnFileFail(Me)
|
||||
Next
|
||||
@@ -1331,9 +1357,9 @@ Retry:
|
||||
''' <summary>
|
||||
''' 下载中断。
|
||||
''' </summary>
|
||||
Public Sub Abort(AbortedTask As LoaderDownload)
|
||||
Public Sub Interrupt(InterruptedTask As LoaderDownload)
|
||||
'从特定任务中移除,如果它还属于其他任务,则继续下载
|
||||
Tasks.Remove(AbortedTask)
|
||||
Tasks.Remove(InterruptedTask)
|
||||
If Tasks.Any Then Return
|
||||
'确认中断
|
||||
SyncLock LockState
|
||||
@@ -1341,9 +1367,9 @@ Retry:
|
||||
Log($"[Download] {LocalName}:已中断,当前状态 {State}")
|
||||
State = NetState.Interrupted
|
||||
End SyncLock
|
||||
AbortInternal()
|
||||
InterruptInternal()
|
||||
End Sub
|
||||
Private Sub AbortInternal()
|
||||
Private Sub InterruptInternal()
|
||||
On Error Resume Next
|
||||
Reset()
|
||||
Interlocked.Decrement(NetManager.FileRemain)
|
||||
@@ -1488,7 +1514,7 @@ FinishExCatch:
|
||||
Next
|
||||
If Not File.HasAvailableSource() Then Throw New ArgumentException("输入的下载链接不正确!")
|
||||
File.LocalPath = File.LocalPath.Replace("/", "\")
|
||||
If Not File.LocalPath.ToLower.Contains(":\") Then Throw New ArgumentException("输入的本地文件地址不正确:" & File.LocalPath)
|
||||
If Not File.LocalPath.Lower.Contains(":\") Then Throw New ArgumentException("输入的本地文件地址不正确:" & File.LocalPath)
|
||||
If File.LocalPath.EndsWithF("\") Then Throw New ArgumentException("请输入含文件名的完整文件路径:" & File.LocalPath)
|
||||
Directory.CreateDirectory(GetPathFromFullPath(File.LocalPath)) '创建目标文件夹
|
||||
Next
|
||||
@@ -1591,7 +1617,7 @@ Retry:
|
||||
'目标文件已存在
|
||||
If File.Check.Check(File.LocalPath) Is Nothing Then Return File.LocalPath
|
||||
'在设置中禁用了复制
|
||||
If Setup.Get("SystemDebugSkipCopy") Then Return Nothing
|
||||
If Settings.Get("SystemDebugSkipCopy") Then Return Nothing
|
||||
'没有可用的检查规则,只能开始下载
|
||||
If File.Check.Hash Is Nothing AndAlso File.Check.ActualSize < 0 Then Return Nothing
|
||||
'大致判断文件类别
|
||||
@@ -1620,7 +1646,7 @@ Retry:
|
||||
'版本 jar 或 json:查找 MC 文件夹下的各个版本文件夹
|
||||
For Each VersionFolder In VersionFolders
|
||||
For Each Candidate In Directory.GetFiles(VersionFolder,
|
||||
"*." & GetFileNameFromPath(File.LocalPath).AfterLast(".").ToLower, SearchOption.TopDirectoryOnly)
|
||||
"*." & GetFileNameFromPath(File.LocalPath).AfterLast(".").Lower, SearchOption.TopDirectoryOnly)
|
||||
'快速进行大小校验
|
||||
If Not Sizes.ContainsKey(Candidate) Then Sizes(Candidate) = New FileInfo(Candidate).Length
|
||||
If File.Check.ActualSize <> Sizes(Candidate) Then Continue For
|
||||
@@ -1682,7 +1708,7 @@ Retry:
|
||||
For Each File In Files
|
||||
If File.State <> NetState.Interrupted Then Continue For
|
||||
If File.Sources.All(Function(s) TypeOf s.Ex Is OperationCanceledException OrElse TypeOf s.Ex Is TimeoutException OrElse TypeOf s.Ex Is ThreadInterruptedException) Then Continue For
|
||||
Dim Detail As String = Join(File.Sources.Select(Function(s) $"{If(s.Ex Is Nothing, "无错误信息。", s.Ex.GetBrief())}({s.Url})"), vbCrLf)
|
||||
Dim Detail As String = File.Sources.Select(Function(s) $"{If(s.Ex Is Nothing, "无错误信息。", s.Ex.GetBrief())}({s.Url})").Join(vbCrLf)
|
||||
[Error] = New Exception("文件下载失败:" & File.LocalPath & vbCrLf &
|
||||
"各下载源的错误如下:" & vbCrLf & Detail, [Error])
|
||||
'上报
|
||||
@@ -1696,24 +1722,24 @@ Retry:
|
||||
End SyncLock
|
||||
'中断所有文件
|
||||
For Each TaskFile In Files
|
||||
TaskFile.Abort(Me)
|
||||
TaskFile.Interrupt(Me)
|
||||
Next
|
||||
'在退出同步锁后再进行日志输出
|
||||
Dim ErrOutput As New List(Of String)
|
||||
For Each Ex As Exception In ExList
|
||||
ErrOutput.Add(Ex.GetDetail())
|
||||
Next
|
||||
Log("[Download] " & Join(ErrOutput.Distinct.ToArray, vbCrLf))
|
||||
Log("[Download] " & ErrOutput.Distinct.Join(vbCrLf))
|
||||
End Sub
|
||||
Public Overrides Sub Abort()
|
||||
Public Overrides Sub Interrupt()
|
||||
SyncLock LockState
|
||||
If State >= LoadState.Finished Then Return
|
||||
State = LoadState.Aborted
|
||||
State = LoadState.Interrupted
|
||||
End SyncLock
|
||||
Log("[Download] " & Name & " 已取消!")
|
||||
'中断所有文件
|
||||
For Each TaskFile In Files
|
||||
TaskFile.Abort(Me)
|
||||
TaskFile.Interrupt(Me)
|
||||
Next
|
||||
End Sub
|
||||
|
||||
@@ -2023,4 +2049,24 @@ Retry:
|
||||
}.Any(Function(k) Detail.ContainsF(k, True))
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 设置是否应当在正版登录时校验 SSL 证书。
|
||||
''' </summary>
|
||||
Public Sub ShouldValidateSslCertificateOnLogin(Enabled As Boolean)
|
||||
ServicePointManager.ServerCertificateValidationCallback =
|
||||
Function(Sender, Certificate, Chain, Failure)
|
||||
Dim Request As HttpWebRequest = TryCast(Sender, HttpWebRequest)
|
||||
If Failure = Net.Security.SslPolicyErrors.None Then Return True '已通过验证
|
||||
'基于 #3018 和 #5879,只在访问正版登录 API 时跳过证书验证
|
||||
Log($"[System] 未通过 SSL 证书验证({Failure}),提供的证书为 {Certificate?.Subject},URL:{Request?.Address}", LogLevel.Debug)
|
||||
If Request Is Nothing Then
|
||||
Return Not Enabled
|
||||
ElseIf Request.Address.Host.Contains("xboxlive") OrElse Request.Address.Host.Contains("minecraftservices") Then
|
||||
Return Not Enabled '根据设置决定是否忽略错误
|
||||
Else
|
||||
Return False
|
||||
End If
|
||||
End Function
|
||||
End Sub
|
||||
|
||||
End Module
|
||||
|
||||
@@ -208,7 +208,7 @@ Public Class ValidateExceptSame
|
||||
If Str Is Nothing Then Return ErrorMessage.Replace("%", "null")
|
||||
For Each Ch As String In Excepts
|
||||
If IgnoreCase Then
|
||||
If Str.ToLower = Ch.ToLower Then Return ErrorMessage.Replace("%", Ch)
|
||||
If Str.Lower = Ch.Lower Then Return ErrorMessage.Replace("%", Ch)
|
||||
Else
|
||||
If Str.Equals(Ch) Then Return ErrorMessage.Replace("%", Ch) '使用 = 不确定是否会忽略大小写
|
||||
End If
|
||||
@@ -218,6 +218,27 @@ Public Class ValidateExceptSame
|
||||
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 必须为特定字符串。
|
||||
''' </summary>
|
||||
Public Class ValidateSame
|
||||
Inherits Validate
|
||||
Public Property Required As String
|
||||
Public Property ErrorMessage As String
|
||||
Public Property IgnoreCase As Boolean = False
|
||||
Public Sub New()
|
||||
End Sub
|
||||
Public Sub New(Required As String, Optional ErrorMessage As String = "输入内容有误", Optional IgnoreCase As Boolean = False)
|
||||
Me.Required = Required
|
||||
Me.ErrorMessage = ErrorMessage
|
||||
Me.IgnoreCase = IgnoreCase
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
Return If(String.Compare(Str, Required, IgnoreCase) = 0, "", ErrorMessage)
|
||||
End Function
|
||||
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 对文件夹名的粗略的特化检测。
|
||||
''' </summary>
|
||||
@@ -355,7 +376,7 @@ Public Class ValidateFolderPath
|
||||
'检查开头
|
||||
If Str.StartsWithF("\\Mac\") Then GoTo Fin
|
||||
For Each Drive As DriveInfo In My.Computer.FileSystem.Drives
|
||||
If Str.ToUpper = Drive.Name Then Return ""
|
||||
If Str.Upper = Drive.Name Then Return ""
|
||||
If Str.StartsWithF(Drive.Name, True) Then GoTo Fin
|
||||
Next
|
||||
Return "文件夹路径头存在错误!"
|
||||
|
||||
Reference in New Issue
Block a user