2.9.1
This commit is contained in:
@@ -12,13 +12,13 @@ Public Module ModBase
|
||||
#Region "声明"
|
||||
|
||||
'下列版本信息由更新器自动修改
|
||||
Public Const VersionBaseName As String = "2.8.13" '不含分支前缀的显示用版本名
|
||||
Public Const VersionStandardCode As String = "2.8.13." & VersionBranchCode '标准格式的四段式版本号
|
||||
Public Const VersionBaseName As String = "2.9.1" '不含分支前缀的显示用版本名
|
||||
Public Const VersionStandardCode As String = "2.9.1." & VersionBranchCode '标准格式的四段式版本号
|
||||
Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
|
||||
#If BETA Then
|
||||
Public Const VersionCode As Integer = 347 'Release
|
||||
Public Const VersionCode As Integer = 352 'Release
|
||||
#Else
|
||||
Public Const VersionCode As Integer = 348 'Snapshot
|
||||
Public Const VersionCode As Integer = 351 'Snapshot
|
||||
#End If
|
||||
'自动生成的版本信息
|
||||
Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
|
||||
@@ -180,10 +180,6 @@ Public Module ModBase
|
||||
''' </summary>
|
||||
Public Const IconButtonServer As String = "M224 160a64 64 0 0 0-64 64v576a64 64 0 0 0 64 64h576a64 64 0 0 0 64-64V224a64 64 0 0 0-64-64H224z m0 384h576v256H224v-256z m192 96v64h320v-64H416z m-128 0v64h64v-64H288zM224 224h576v256H224V224z m192 96v64h320v-64H416z m-128 0v64h64v-64H288z"
|
||||
''' <summary>
|
||||
''' 图标,向右箭头,1x
|
||||
''' </summary>
|
||||
Public Const IconButtonRight As String = "M757.792745 435.407215L419.597482 96.904967c-40.010393-40.010393-104.886579-40.010393-144.896972 0-40.010393 40.010393-40.010393 104.988908 0 144.9993L540.344959 507.855701 274.70051 773.807135c-40.010393 40.112721-40.010393 104.988908 0 144.9993 40.010393 40.010393 104.886579 40.010393 144.896972 0l338.092935-338.39992c40.112721-40.010393 40.112721-104.988908 0.102328-144.9993z"
|
||||
''' <summary>
|
||||
''' 图标,音符,1x
|
||||
''' </summary>
|
||||
Public Const IconMusic As String = "M348.293565 716.53287V254.797913c0-41.672348 28.004174-78.358261 68.919652-90.37913L815.994435 40.826435c62.775652-18.610087 125.907478 26.579478 125.907478 89.933913v539.158261c8.013913 42.25113-8.94887 89.177043-47.014956 127.109565a232.848696 232.848696 0 0 1-170.785392 65.758609c-61.885217-2.938435-111.081739-33.435826-129.113043-80.050087-18.031304-46.614261-2.137043-102.177391 41.672348-145.853218a232.848696 232.848696 0 0 1 170.785391-65.80313c21.014261 1.024 40.514783 5.164522 57.878261 12.065391V233.338435c0-12.109913-10.551652-20.034783-20.569044-20.034783a24.620522 24.620522 0 0 0-5.787826 0.934957L439.785739 338.18713a19.545043 19.545043 0 0 0-14.825739 19.144348v438.984348H423.846957c11.53113 43.987478-5.164522 94.208-45.412174 134.322087a232.848696 232.848696 0 0 1-170.785392 65.758609c-61.885217-2.938435-111.081739-33.435826-129.113043-80.050087-18.031304-46.614261-2.137043-102.177391 41.672348-145.853218a232.848696 232.848696 0 0 1 170.785391-65.80313c20.791652 1.024 40.069565 5.075478 57.299478 11.842783z"
|
||||
@@ -961,17 +957,17 @@ Public Module ModBase
|
||||
''' <param name="FileFilter">要求的格式。如:“常用图片文件(*.png;*.jpg)|*.png;*.jpg”。</param>
|
||||
''' <param name="Title">弹窗的标题。</param>
|
||||
''' <param name="FileName">默认的文件名。</param>
|
||||
Public Function SelectAs(Title As String, FileName As String, Optional FileFilter As String = Nothing, Optional DefaultDir As String = Nothing) As String
|
||||
Public Function SelectSaveFile(Title As String, FileName As String, Optional FileFilter As String = Nothing, Optional InitialDirectory As String = Nothing) As String
|
||||
Using fileDialog As New Forms.SaveFileDialog
|
||||
fileDialog.AddExtension = True
|
||||
fileDialog.AutoUpgradeEnabled = True
|
||||
fileDialog.Title = Title
|
||||
fileDialog.FileName = FileName
|
||||
If FileFilter IsNot Nothing Then fileDialog.Filter = FileFilter
|
||||
If DefaultDir IsNot Nothing Then fileDialog.InitialDirectory = DefaultDir
|
||||
If Not String.IsNullOrEmpty(InitialDirectory) AndAlso Directory.Exists(InitialDirectory) Then fileDialog.InitialDirectory = InitialDirectory
|
||||
fileDialog.ShowDialog()
|
||||
SelectAs = If(fileDialog.FileName.Contains(":\"), fileDialog.FileName, "")
|
||||
Log("[UI] 选择文件返回:" & SelectAs)
|
||||
SelectSaveFile = If(fileDialog.FileName.Contains(":\"), fileDialog.FileName, "")
|
||||
Log("[UI] 选择文件返回:" & SelectSaveFile)
|
||||
End Using
|
||||
End Function
|
||||
''' <summary>
|
||||
@@ -979,7 +975,7 @@ Public Module ModBase
|
||||
''' </summary>
|
||||
''' <param name="FileFilter">要求的格式。如:“常用图片文件(*.png;*.jpg)|*.png;*.jpg”。</param>
|
||||
''' <param name="Title">弹窗的标题。</param>
|
||||
Public Function SelectFile(FileFilter As String, Title As String) As String
|
||||
Public Function SelectFile(FileFilter As String, Title As String, Optional InitialDirectory As String = Nothing) As String
|
||||
Using fileDialog As New Forms.OpenFileDialog
|
||||
fileDialog.AddExtension = True
|
||||
fileDialog.AutoUpgradeEnabled = True
|
||||
@@ -988,6 +984,7 @@ Public Module ModBase
|
||||
fileDialog.Multiselect = False
|
||||
fileDialog.Title = Title
|
||||
fileDialog.ValidateNames = True
|
||||
If Not String.IsNullOrEmpty(InitialDirectory) AndAlso Directory.Exists(InitialDirectory) Then fileDialog.InitialDirectory = InitialDirectory
|
||||
fileDialog.ShowDialog()
|
||||
Log("[UI] 选择单个文件返回:" & fileDialog.FileName)
|
||||
Return fileDialog.FileName
|
||||
@@ -1084,6 +1081,37 @@ Re:
|
||||
End Try
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 获取文件 SHA512,若失败则返回空字符串。
|
||||
''' </summary>
|
||||
Public Function GetFileSHA512(FilePath As String) As String
|
||||
Dim Retry As Boolean = False
|
||||
Re:
|
||||
Try
|
||||
''检测该文件是否在下载中,若在下载则放弃检测
|
||||
'If IgnoreOnDownloading AndAlso NetManage.Files.ContainsKey(FilePath) AndAlso NetManage.Files(FilePath).State <= NetState.Merge Then Return ""
|
||||
'获取 SHA512
|
||||
Dim file As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
|
||||
Dim sha512 As SHA512 = New SHA512CryptoServiceProvider()
|
||||
Dim retval As Byte() = sha512.ComputeHash(file)
|
||||
file.Close()
|
||||
Dim Result As New StringBuilder()
|
||||
For i As Integer = 0 To retval.Length - 1
|
||||
Result.Append(retval(i).ToString("x2"))
|
||||
Next
|
||||
Return Result.ToString
|
||||
Catch ex As Exception
|
||||
If Retry OrElse TypeOf ex Is FileNotFoundException Then
|
||||
Log(ex, "获取文件 SHA512 失败:" & FilePath)
|
||||
Return ""
|
||||
Else
|
||||
Retry = True
|
||||
Log(ex, "获取文件 SHA512 可重试失败:" & FilePath, LogLevel.Normal)
|
||||
Thread.Sleep(RandomInteger(200, 500))
|
||||
GoTo Re
|
||||
End If
|
||||
End Try
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 获取文件 SHA256,若失败则返回空字符串。
|
||||
''' </summary>
|
||||
Public Function GetFileSHA256(FilePath As String) As String
|
||||
@@ -1094,8 +1122,8 @@ Re:
|
||||
'If IgnoreOnDownloading AndAlso NetManage.Files.ContainsKey(FilePath) AndAlso NetManage.Files(FilePath).State <= NetState.Merge Then Return ""
|
||||
'获取 SHA256
|
||||
Dim file As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
|
||||
Dim sha1 As SHA256 = New SHA256CryptoServiceProvider()
|
||||
Dim retval As Byte() = sha1.ComputeHash(file)
|
||||
Dim sha256 As SHA256 = New SHA256CryptoServiceProvider()
|
||||
Dim retval As Byte() = sha256.ComputeHash(file)
|
||||
file.Close()
|
||||
Dim Result As New StringBuilder()
|
||||
For i As Integer = 0 To retval.Length - 1
|
||||
@@ -1144,7 +1172,7 @@ Re:
|
||||
End Try
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 获取流 SHA1,若失败则返回空字符串。
|
||||
''' 获取流的 SHA1,若失败则返回空字符串。
|
||||
''' </summary>
|
||||
Public Function GetAuthSHA1(Stream As Stream) As String
|
||||
Try
|
||||
@@ -1336,11 +1364,22 @@ RetryDir:
|
||||
''' 遍历文件夹中的所有文件。
|
||||
''' </summary>
|
||||
Public Function EnumerateFiles(Directory As String) As IEnumerable(Of FileInfo)
|
||||
Dim Info As New DirectoryInfo(Directory)
|
||||
Dim Info As New DirectoryInfo(ShortenPath(Directory))
|
||||
If Not Info.Exists Then Return New List(Of FileInfo)
|
||||
Return Info.EnumerateFiles("*", SearchOption.AllDirectories)
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 若路径长度大于指定值,则将长路径转换为短路径。
|
||||
''' </summary>
|
||||
Public Function ShortenPath(LongPath As String, Optional ShortenThreshold As Integer = 247) As String
|
||||
If LongPath.Length <= ShortenThreshold Then Return LongPath
|
||||
Dim ShortPath As New StringBuilder(260)
|
||||
GetShortPathName(LongPath, ShortPath, 260)
|
||||
Return ShortPath.ToString
|
||||
End Function
|
||||
Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" (ByVal lpszLongPath As String, ByVal lpszShortPath As StringBuilder, ByVal cchBuffer As Integer) As Integer
|
||||
|
||||
#End Region
|
||||
|
||||
#Region "文本"
|
||||
@@ -1350,8 +1389,8 @@ RetryDir:
|
||||
''' <summary>
|
||||
''' 提取 Exception 的具体描述与堆栈。
|
||||
''' </summary>
|
||||
''' <param name="ShowAllTrace">是否必须显示所有堆栈。通常用于判定堆栈信息。</param>
|
||||
Public Function GetExceptionDetail(Ex As Exception, Optional ShowAllTrace As Boolean = False) As String
|
||||
''' <param name="ShowAllStacks">是否必须显示所有堆栈。通常用于判定堆栈信息。</param>
|
||||
Public Function GetExceptionDetail(Ex As Exception, Optional ShowAllStacks As Boolean = False) As String
|
||||
If Ex Is Nothing Then Return "无可用错误信息!"
|
||||
|
||||
'获取最底层的异常
|
||||
@@ -1362,19 +1401,20 @@ RetryDir:
|
||||
|
||||
'获取各级错误的描述与堆栈信息
|
||||
Dim DescList As New List(Of String)
|
||||
Dim StackList As New List(Of String)
|
||||
Dim IsInner As Boolean = False
|
||||
Do Until Ex Is Nothing
|
||||
DescList.Add(Ex.Message.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr & vbCr, vbCr).Replace(vbCr, vbCrLf))
|
||||
DescList.Add(If(IsInner, "→ ", "") & Ex.Message.Replace(vbLf, vbCr).Replace(vbCr & vbCr, vbCr).Replace(vbCr, vbCrLf))
|
||||
If Ex.StackTrace IsNot Nothing Then
|
||||
For Each St As String In Ex.StackTrace.Split(vbCrLf)
|
||||
If ShowAllTrace OrElse St.ToLower.Contains("pcl") Then StackList.Add(St.Replace(vbCr, String.Empty).Replace(vbLf, String.Empty))
|
||||
For Each Stack As String In Ex.StackTrace.Split(vbCrLf.ToCharArray, StringSplitOptions.RemoveEmptyEntries)
|
||||
If ShowAllStacks OrElse Stack.ContainsF("pcl", True) Then
|
||||
DescList.Add(Stack.Replace(vbCr, String.Empty).Replace(vbLf, String.Empty))
|
||||
End If
|
||||
Next
|
||||
End If
|
||||
If Ex.GetType.FullName <> "System.Exception" Then DescList.Add(" 错误类型:" & Ex.GetType.FullName)
|
||||
Ex = Ex.InnerException
|
||||
IsInner = True
|
||||
Loop
|
||||
DescList = DescList.Distinct.ToList
|
||||
Dim Desc As String = Join(DescList, vbCrLf & "→ ")
|
||||
Dim Stack As String = If(StackList.Any, vbCrLf & Join(StackList, vbCrLf), "")
|
||||
|
||||
'常见错误(记得同时修改下面的)
|
||||
Dim CommonReason As String = Nothing
|
||||
@@ -1387,20 +1427,15 @@ RetryDir:
|
||||
ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then
|
||||
CommonReason = "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。"
|
||||
ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
|
||||
"操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) Desc.Contains(s)) Then
|
||||
"操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) DescList.Any(Function(l) l.Contains(s))) Then
|
||||
CommonReason = "你的网络环境不佳,导致难以连接到服务器。请检查网络,多重试几次,或尝试使用 VPN。"
|
||||
End If
|
||||
|
||||
'获取错误类型
|
||||
Dim TypeDesc As String = If(InnerEx.GetType.FullName = "System.Exception", "", vbCrLf & "错误类型:" & InnerEx.GetType.FullName)
|
||||
|
||||
'构造输出信息
|
||||
If CommonReason Is Nothing Then
|
||||
Return Desc & Stack & TypeDesc
|
||||
Return DescList.Join(vbCrLf)
|
||||
Else
|
||||
Dim Result As String = CommonReason & vbCrLf & DescList.First & vbCrLf & "————————————" & vbCrLf
|
||||
DescList(0) = "详细错误信息:"
|
||||
Return Result & Join(DescList, vbCrLf & "→ ") & Stack & TypeDesc
|
||||
Return CommonReason & vbCrLf & vbCrLf & "————————————" & vbCrLf & "详细错误信息:" & vbCrLf & DescList.Join(vbCrLf)
|
||||
End If
|
||||
End Function
|
||||
''' <summary>
|
||||
@@ -1444,7 +1479,7 @@ RetryDir:
|
||||
Return CommonReason & "详细错误:" & DescList.First
|
||||
Else
|
||||
DescList.Reverse() '让最深层错误在最左边
|
||||
Return Join(DescList, " → ")
|
||||
Return Join(DescList, " ← ")
|
||||
End If
|
||||
End Function
|
||||
|
||||
@@ -1722,6 +1757,21 @@ RetryDir:
|
||||
Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("'", "'").
|
||||
Replace("""", """).Replace(vbCrLf, "
")
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 为字符串进行 Like 关键字转义。
|
||||
''' </summary>
|
||||
Public Function EscapeLikePattern(input As String) As String
|
||||
Dim sb As New StringBuilder()
|
||||
For Each c As Char In input
|
||||
Select Case c
|
||||
Case "["c, "]"c, "*"c, "?"c, "#"c
|
||||
sb.Append("["c).Append(c).Append("]"c)
|
||||
Case Else
|
||||
sb.Append(c)
|
||||
End Select
|
||||
Next
|
||||
Return sb.ToString()
|
||||
End Function
|
||||
|
||||
'正则
|
||||
''' <summary>
|
||||
@@ -2134,6 +2184,7 @@ NextElement:
|
||||
''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
|
||||
''' <param name="Arguments">运行参数。</param>
|
||||
Public Sub ShellOnly(FileName As String, Optional Arguments As String = "")
|
||||
FileName = ShortenPath(FileName)
|
||||
Using Program As New Process
|
||||
Program.StartInfo.Arguments = Arguments
|
||||
Program.StartInfo.FileName = FileName
|
||||
@@ -2179,7 +2230,7 @@ NextElement:
|
||||
.CreateNoWindow = True,
|
||||
.RedirectStandardError = True,
|
||||
.RedirectStandardOutput = True,
|
||||
.WorkingDirectory = If(WorkingDirectory, Path.TrimEnd("\"c))
|
||||
.WorkingDirectory = ShortenPath(If(WorkingDirectory, Path.TrimEnd("\"c)))
|
||||
}
|
||||
If WorkingDirectory IsNot Nothing Then
|
||||
If Info.EnvironmentVariables.ContainsKey("appdata") Then
|
||||
@@ -2288,6 +2339,27 @@ NextElement:
|
||||
Return New List(Of T)(list)
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 尝试从字典中获取某项,如果该项不存在,则返回默认值。
|
||||
''' </summary>
|
||||
<Extension> Public Function GetOrDefault(Of TKey, TValue)(Dict As Dictionary(Of TKey, TValue), Key As TKey, Optional DefaultValue As TValue = Nothing) As TValue
|
||||
If Dict.ContainsKey(Key) Then
|
||||
Return Dict(Key)
|
||||
Else
|
||||
Return DefaultValue
|
||||
End If
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 将某项添加到以列表作为值的字典中。
|
||||
''' </summary>
|
||||
<Extension> Public Sub AddToList(Of TKey, TValue)(Dict As Dictionary(Of TKey, List(Of TValue)), Key As TKey, Value As TValue)
|
||||
If Dict.ContainsKey(Key) Then
|
||||
Dict(Key).Add(Value)
|
||||
Else
|
||||
Dict.Add(Key, New List(Of TValue) From {Value})
|
||||
End If
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' 获取程序启动参数。
|
||||
''' </summary>
|
||||
@@ -2337,11 +2409,18 @@ NextElement:
|
||||
End Try
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 打开 explorer。注意参数中的路径要尽量加上双引号!
|
||||
''' 打开 explorer。
|
||||
''' 若不以 \ 结尾,则将视作文件路径,打开并选中此文件。
|
||||
''' </summary>
|
||||
Public Sub OpenExplorer(Argument As String)
|
||||
Public Sub OpenExplorer(Location As String)
|
||||
Try
|
||||
ShellOnly("explorer.exe", Argument)
|
||||
Location = ShortenPath(Location.Replace("/", "\").Trim(" "c, """"c))
|
||||
Log("[System] 正在打开资源管理器:" & Location)
|
||||
If Location.EndsWith("\") Then
|
||||
ShellOnly(Location)
|
||||
Else
|
||||
ShellOnly("explorer", $"/select,""{Location}""")
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Log(ex, "打开资源管理器失败,请尝试关闭安全软件(如 360 安全卫士)", LogLevel.Msgbox)
|
||||
End Try
|
||||
@@ -2792,7 +2871,7 @@ Retry:
|
||||
On Error Resume Next
|
||||
FeedbackInfo()
|
||||
If ForceOpenLog OrElse (ShowMsgbox AndAlso MyMsgBox("若你在汇报一个 Bug,请点击 打开文件夹 按钮,并上传 Log(1~5).txt 中包含错误信息的文件。" & vbCrLf & "游戏崩溃一般与启动器无关,请不要因为游戏崩溃而提交反馈。", "反馈提交提醒", "打开文件夹", "不需要") = 1) Then
|
||||
OpenExplorer("""" & Path & "PCL\""")
|
||||
OpenExplorer(Path & "PCL\Log1.txt")
|
||||
End If
|
||||
OpenWebsite("https://github.com/Hex-Dragon/PCL2/issues/")
|
||||
End Sub
|
||||
@@ -2868,4 +2947,88 @@ Retry:
|
||||
|
||||
#End Region
|
||||
|
||||
End Module
|
||||
End Module
|
||||
|
||||
#Region "WPF"
|
||||
|
||||
''' <summary>
|
||||
''' 对数据绑定进行加法运算,使用参数决定加数。
|
||||
''' </summary>
|
||||
Public Class AdditionConverter
|
||||
Implements IValueConverter
|
||||
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
|
||||
If value Is Nothing Then Return 0
|
||||
Dim before As Double
|
||||
If Not Double.TryParse(value.ToString(), before) Then Return 0
|
||||
Dim scale As Double = 1
|
||||
If parameter IsNot Nothing Then Double.TryParse(parameter.ToString(), scale)
|
||||
Return before + scale
|
||||
End Function
|
||||
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
|
||||
If value Is Nothing Then Return Binding.DoNothing
|
||||
Dim before As Double
|
||||
If Not Double.TryParse(value.ToString(), before) Then Return Binding.DoNothing
|
||||
Dim scale As Double = 1
|
||||
If parameter IsNot Nothing Then Double.TryParse(parameter.ToString(), scale)
|
||||
If scale = 0 Then Return Binding.DoNothing
|
||||
Return before - scale
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 对数据绑定进行乘法运算,使用参数决定乘数。
|
||||
''' </summary>
|
||||
Public Class MultiplicationConverter
|
||||
Implements IValueConverter
|
||||
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
|
||||
If value Is Nothing Then Return 0
|
||||
Dim before As Double
|
||||
If Not Double.TryParse(value.ToString(), before) Then Return 0
|
||||
Dim scale As Double = 1
|
||||
If parameter IsNot Nothing Then Double.TryParse(parameter.ToString(), scale)
|
||||
Return before * scale
|
||||
End Function
|
||||
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
|
||||
If value Is Nothing Then Return Binding.DoNothing
|
||||
Dim before As Double
|
||||
If Not Double.TryParse(value.ToString(), before) Then Return Binding.DoNothing
|
||||
Dim scale As Double = 1
|
||||
If parameter IsNot Nothing Then Double.TryParse(parameter.ToString(), scale)
|
||||
If scale = 0 Then Return Binding.DoNothing
|
||||
Return before / scale
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 将取反的 Boolean 绑定到 Visibility。
|
||||
''' </summary>
|
||||
Public Class InverseBooleanToVisibilityConverter
|
||||
Implements IValueConverter
|
||||
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
|
||||
If value Is Nothing Then Return Visibility.Visible
|
||||
Dim boolValue As Boolean
|
||||
Return If(Boolean.TryParse(value.ToString(), boolValue), If(boolValue, Visibility.Collapsed, Visibility.Visible), Visibility.Visible)
|
||||
End Function
|
||||
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
|
||||
If value Is Nothing Then Return False
|
||||
Return If(TypeOf value Is Visibility, value <> Visibility.Visible, False)
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 将 Boolean 取反。
|
||||
''' </summary>
|
||||
Public Class InverseBooleanConverter
|
||||
Implements IValueConverter
|
||||
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
|
||||
If value Is Nothing Then Return False
|
||||
Dim boolValue As Boolean
|
||||
Return If(Boolean.TryParse(value.ToString(), boolValue), Not boolValue, False)
|
||||
End Function
|
||||
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
|
||||
If value Is Nothing Then Return False
|
||||
Return If(Boolean.TryParse(value.ToString(), value), Not value, False)
|
||||
End Function
|
||||
End Class
|
||||
|
||||
#End Region
|
||||
|
||||
@@ -315,23 +315,26 @@
|
||||
Sub()
|
||||
Try
|
||||
IsForceRestarting = IsForceRestart
|
||||
If ModeDebug Then Log("[Loader] 加载线程 " & Name & " (" & Thread.CurrentThread.ManagedThreadId & ") 已" & If(IsForceRestarting, "强制", "") & "启动")
|
||||
If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已{If(IsForceRestarting, "强制", "")}启动")
|
||||
LoadDelegate(Me)
|
||||
If IsAborted Then
|
||||
Log("[Loader] 加载线程 " & Name & " (" & Thread.CurrentThread.ManagedThreadId & ") 已中断但线程正常运行至结束,输出被弃用(最新线程:" & If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId) & ")", LogLevel.Developer)
|
||||
Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已中断但线程正常运行至结束,输出被弃用(最新线程:{If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId)})", LogLevel.Developer)
|
||||
Else
|
||||
RaisePreviewFinish()
|
||||
State = LoadState.Finished
|
||||
LastFinishedTime = GetTimeTick() '未中断,本次输出有效
|
||||
End If
|
||||
Catch ex As CancelledException
|
||||
If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发取消中断,已完成 {Math.Round(Progress * 100)}%")
|
||||
If Not IsAborted Then State = LoadState.Aborted
|
||||
Catch ex As ThreadInterruptedException
|
||||
If ModeDebug Then Log(ex, "加载线程 " & Name & " (" & Thread.CurrentThread.ManagedThreadId & ") 已触发线程中断")
|
||||
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
|
||||
Catch ex As Exception
|
||||
If IsAborted Then Exit Sub
|
||||
Log(ex, "加载线程 " & Name & " (" & Thread.CurrentThread.ManagedThreadId & ") 发生运行时错误", LogLevel.Developer)
|
||||
Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 出错,已完成 {Math.Round(Progress * 100)}%", LogLevel.Developer)
|
||||
[Error] = ex
|
||||
State = LoadState.Failed
|
||||
End Try
|
||||
@@ -347,7 +350,7 @@
|
||||
End Sub
|
||||
Private Sub TriggerThreadAbort()
|
||||
If LastRunningThread Is Nothing Then Exit Sub
|
||||
If ModeDebug Then Log("[Loader] 加载线程 " & Name & " (" & LastRunningThread.ManagedThreadId & ") 已中断")
|
||||
If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({LastRunningThread.ManagedThreadId}) 已中断")
|
||||
If LastRunningThread.IsAlive Then LastRunningThread.Interrupt()
|
||||
LastRunningThread = Nothing
|
||||
End Sub
|
||||
|
||||
@@ -220,12 +220,12 @@ RequestFinished:
|
||||
''' 以多线程下载网页文件的方式获取网页源代码。
|
||||
''' </summary>
|
||||
''' <param name="Url">网页的 Url。</param>
|
||||
Public Function NetGetCodeByDownload(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String
|
||||
Public Function NetGetCodeByLoader(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String
|
||||
Dim Temp As String = PathTemp & "Cache\Code\" & Url.GetHashCode() & "_" & GetUuid()
|
||||
Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile({Url}, Temp, New FileChecker With {.IsJson = IsJson}, UseBrowserUserAgent)})
|
||||
Try
|
||||
NewTask.WaitForExitTime(Timeout, TimeoutMessage:="连接服务器超时(" & Url & ")")
|
||||
NetGetCodeByDownload = ReadFile(Temp)
|
||||
NetGetCodeByLoader = ReadFile(Temp)
|
||||
File.Delete(Temp)
|
||||
Finally
|
||||
NewTask.Abort()
|
||||
@@ -235,12 +235,12 @@ RequestFinished:
|
||||
''' 以多线程下载网页文件的方式获取网页源代码。
|
||||
''' </summary>
|
||||
''' <param name="Urls">网页的 Url 列表。</param>
|
||||
Public Function NetGetCodeByDownload(Urls As IEnumerable(Of String), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String
|
||||
Public Function NetGetCodeByLoader(Urls As IEnumerable(Of String), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String
|
||||
Dim Temp As String = PathTemp & "Cache\Code\" & Urls.First.GetHashCode() & "_" & GetUuid()
|
||||
Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile(Urls, Temp, New FileChecker With {.IsJson = IsJson}, UseBrowserUserAgent)})
|
||||
Try
|
||||
NewTask.WaitForExitTime(Timeout, TimeoutMessage:="连接服务器超时(第一下载源:" & Urls.First & ")")
|
||||
NetGetCodeByDownload = ReadFile(Temp)
|
||||
NetGetCodeByLoader = ReadFile(Temp)
|
||||
File.Delete(Temp)
|
||||
Finally
|
||||
NewTask.Abort()
|
||||
@@ -248,13 +248,12 @@ RequestFinished:
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 从网络中直接下载文件。这不能下载 CDN 中的文件。
|
||||
''' 使用 WebClient 从网络中下载文件。这不能下载 CDN 中的文件。
|
||||
''' </summary>
|
||||
''' <param name="Url">网络 Url。</param>
|
||||
''' <param name="LocalFile">下载的本地地址。</param>
|
||||
Public Sub NetDownload(Url As String, LocalFile As String, Optional UseBrowserUserAgent As Boolean = False)
|
||||
Public Sub NetDownloadByClient(Url As String, LocalFile As String, Optional UseBrowserUserAgent As Boolean = False)
|
||||
Log("[Net] 直接下载文件:" & Url)
|
||||
|
||||
'初始化
|
||||
Try
|
||||
'建立目录
|
||||
@@ -262,9 +261,8 @@ RequestFinished:
|
||||
'尝试删除原文件
|
||||
File.Delete(LocalFile)
|
||||
Catch ex As Exception
|
||||
Throw New WebException("预处理下载文件路径失败(" & LocalFile & ")。", ex)
|
||||
Throw New WebException($"预处理下载文件路径失败({LocalFile})", ex)
|
||||
End Try
|
||||
|
||||
'下载
|
||||
Using Client As New WebClient
|
||||
Try
|
||||
@@ -272,10 +270,41 @@ RequestFinished:
|
||||
Client.DownloadFile(Url, LocalFile)
|
||||
Catch ex As Exception
|
||||
File.Delete(LocalFile)
|
||||
Throw New WebException("直接下载文件失败(" & Url & ")。", ex)
|
||||
Throw New WebException($"直接下载文件失败({Url})", ex)
|
||||
End Try
|
||||
End Using
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' 简单的多线程下载文件。可以下载 CDN 中的文件。
|
||||
''' </summary>
|
||||
''' <param name="Url">文件的 Url。</param>
|
||||
''' <param name="LocalFile">下载的本地地址。</param>
|
||||
Public Sub NetDownloadByLoader(Url As String, LocalFile As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False)
|
||||
Dim NewTask As New LoaderDownload("文件下载 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile({Url}, LocalFile, Check, UseBrowserUserAgent)})
|
||||
Try
|
||||
NewTask.WaitForExit(LoaderToSyncProgress:=LoaderToSyncProgress)
|
||||
Catch ex As Exception
|
||||
Throw New WebException($"多线程直接下载文件失败({Url})", ex)
|
||||
Finally
|
||||
NewTask.Abort()
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' 简单的多线程下载文件。可以下载 CDN 中的文件。
|
||||
''' </summary>
|
||||
''' <param name="Urls">文件的 Url 列表。</param>
|
||||
''' <param name="LocalFile">下载的本地地址。</param>
|
||||
Public Sub NetDownloadByLoader(Urls As IEnumerable(Of String), LocalFile As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False)
|
||||
Dim NewTask As New LoaderDownload("文件下载 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile(Urls, LocalFile, Check, UseBrowserUserAgent)})
|
||||
Try
|
||||
NewTask.WaitForExit(LoaderToSyncProgress:=LoaderToSyncProgress)
|
||||
Catch ex As Exception
|
||||
Throw New WebException($"多线程直接下载文件失败(第一下载源:" & Urls.First() & ")", ex)
|
||||
Finally
|
||||
NewTask.Abort()
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
@@ -1885,7 +1914,7 @@ Retry:
|
||||
For Each File As NetFile In WaitingFiles
|
||||
If NetTaskThreadCount >= NetTaskThreadLimit Then Continue While '最大线程数检查
|
||||
Dim NewThread = File.TryBeginThread()
|
||||
If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(100) '减少 BMCLAPI 请求频率,避免 Too Many Requests
|
||||
If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(30) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次)
|
||||
Next
|
||||
'为进行中的文件追加线程
|
||||
If Speed >= NetTaskSpeedLimitLow Then Continue While '下载速度足够,无需新增
|
||||
@@ -1905,7 +1934,7 @@ Retry:
|
||||
'新增线程
|
||||
If PreparingCount > DownloadingCount Then Continue For '准备中的线程已多于下载中的线程,不再新增
|
||||
Dim NewThread = File.TryBeginThread()
|
||||
If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(100) '减少 BMCLAPI 请求频率,避免 Too Many Requests
|
||||
If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(30) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次)
|
||||
Next
|
||||
End While
|
||||
Catch ex As Exception
|
||||
|
||||
@@ -262,7 +262,6 @@ Public Class ValidateFileName
|
||||
Public Property IgnoreCase As Boolean = True
|
||||
Public Property ParentFolder As String = Nothing
|
||||
Public Property RequireParentFolderExists = True
|
||||
Public Property AllowNull As Boolean = False
|
||||
Public Sub New()
|
||||
End Sub
|
||||
Public Sub New(Name As String, Optional UseMinecraftCharCheck As Boolean = True, Optional IgnoreCase As Boolean = True)
|
||||
@@ -272,16 +271,14 @@ Public Class ValidateFileName
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
Try
|
||||
If Not AllowNull Then
|
||||
'检查是否为空
|
||||
Dim NullCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
|
||||
If Not NullCheck = "" Then Return NullCheck
|
||||
End If
|
||||
'检查是否为空
|
||||
Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
|
||||
If Not LengthCheck = "" Then Return LengthCheck
|
||||
'检查空格
|
||||
If Str.StartsWithF(" ") Then Return "文件名不能以空格开头!"
|
||||
If Str.EndsWithF(" ") Then Return "文件名不能以空格结尾!"
|
||||
'检查长度
|
||||
Dim LengthCheck = New ValidateLength(If(AllowNull, 0, 1), 253).Validate(Str & If(ParentFolder, ""))
|
||||
LengthCheck = New ValidateLength(1, 253).Validate(Str & If(ParentFolder, ""))
|
||||
If Not LengthCheck = "" Then Return LengthCheck
|
||||
'检查尾部小数点
|
||||
If Str.EndsWithF(".") Then Return "文件名不能以小数点结尾!"
|
||||
@@ -318,7 +315,6 @@ End Class
|
||||
Public Class ValidateFolderPath
|
||||
Inherits Validate
|
||||
Public Property UseMinecraftCharCheck As Boolean = True
|
||||
Public Property AllowNull As Boolean = False
|
||||
Public Sub New()
|
||||
End Sub
|
||||
Public Sub New(UseMinecraftCharCheck As Boolean)
|
||||
@@ -330,9 +326,9 @@ Public Class ValidateFolderPath
|
||||
If Not Str.TrimEnd("\").EndsWithF(":") Then Str = Str.TrimEnd("\")
|
||||
'检查是否为空
|
||||
Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
|
||||
If Not AllowNull AndAlso Not LengthCheck = "" Then Return LengthCheck
|
||||
If Not LengthCheck = "" Then Return LengthCheck
|
||||
'检查长度
|
||||
LengthCheck = New ValidateLength(If(AllowNull, 0, 1), 254).Validate(Str)
|
||||
LengthCheck = New ValidateLength(1, 254).Validate(Str)
|
||||
If Not LengthCheck = "" Then Return LengthCheck
|
||||
'检查开头
|
||||
If Str.StartsWithF("\\Mac\") Then GoTo Fin
|
||||
|
||||
Reference in New Issue
Block a user