🔀 合并上游分支
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
Imports System.IO.Compression
|
||||
Imports System.Globalization
|
||||
Imports System.IO.Compression
|
||||
Imports System.Runtime.CompilerServices
|
||||
Imports System.Security.Cryptography
|
||||
Imports System.Security.Principal
|
||||
@@ -11,12 +12,13 @@ Public Module ModBase
|
||||
#Region "声明"
|
||||
|
||||
'下列版本信息由更新器自动修改
|
||||
Public Const VersionBaseName As String = "2.8.0" '不含分支前缀的显示用版本名
|
||||
Public Const VersionStandardCode As String = "2.8.0." & VersionBranchCode '标准格式的四段式版本号
|
||||
Public Const VersionBaseName As String = "2.8.12" '不含分支前缀的显示用版本名
|
||||
Public Const VersionStandardCode As String = "2.8.12." & VersionBranchCode '标准格式的四段式版本号
|
||||
Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
|
||||
#If BETA Then
|
||||
Public Const VersionCode As Integer = 326 'Release
|
||||
Public Const VersionCode As Integer = 347 'Release
|
||||
#Else
|
||||
Public Const VersionCode As Integer = 327 'Snapshot
|
||||
Public Const VersionCode As Integer = 346 'Snapshot
|
||||
#End If
|
||||
'自动生成的版本信息
|
||||
Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
|
||||
@@ -64,7 +66,7 @@ Public Module ModBase
|
||||
''' </summary>
|
||||
Public ApplicationOpenTime As Date = Date.Now
|
||||
''' <summary>
|
||||
''' 设备唯一标识符。
|
||||
''' 识别码。
|
||||
''' </summary>
|
||||
Public UniqueAddress As String = SecretGetUniqueAddress()
|
||||
''' <summary>
|
||||
@@ -80,13 +82,9 @@ Public Module ModBase
|
||||
''' </summary>
|
||||
Public IsGBKEncoding As Boolean = Encoding.Default.CodePage = 936
|
||||
''' <summary>
|
||||
''' 操作系统版本。Win10 为 10.0。
|
||||
''' </summary>
|
||||
Public OsVersion As Version = Environment.OSVersion.Version
|
||||
''' <summary>
|
||||
''' 系统盘盘符,以 \ 结尾。例如 “C:\”。
|
||||
''' </summary>
|
||||
Public OsDrive As String = Environment.GetLogicalDrives().First.ToUpper.First & ":\"
|
||||
Public OsDrive As String = Environment.GetLogicalDrives().Where(Function(p) Directory.Exists(p)).First.ToUpper.First & ":\" '#3799
|
||||
''' <summary>
|
||||
''' 程序的缓存文件夹路径,以 \ 结尾。
|
||||
''' </summary>
|
||||
@@ -178,6 +176,10 @@ Public Module ModBase
|
||||
''' </summary>
|
||||
Public Const IconButtonOffline As String = "M533.293176 788.841412a60.235294 60.235294 0 1 1 85.202824 85.202823l-42.616471 42.586353c-129.355294 129.385412-339.124706 129.385412-468.510117 0-129.385412-129.385412-129.385412-339.124706 0-468.510117l42.586353-42.616471a60.235294 60.235294 0 1 1 85.202823 85.202824l-42.61647 42.586352a210.823529 210.823529 0 1 0 298.164706 298.164706l42.586352-42.61647z m255.548236-255.548236l42.61647-42.586352a210.823529 210.823529 0 1 0-298.164706-298.164706l-42.586352 42.61647a60.235294 60.235294 0 1 1-85.202824-85.202823l42.616471-42.586353c129.355294-129.385412 339.124706-129.385412 468.510117 0 129.385412 129.385412 129.385412 339.124706 0 468.510117l-42.586353 42.616471a60.235294 60.235294 0 1 1-85.202823-85.202824zM192.542118 192.542118a60.235294 60.235294 0 0 1 85.202823 0l553.712941 553.712941a60.235294 60.235294 0 0 1-85.202823 85.202823L192.542118 277.744941a60.235294 60.235294 0 0 1 0-85.202823z"
|
||||
''' <summary>
|
||||
''' 图标,服务端,1x
|
||||
''' </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"
|
||||
@@ -704,7 +706,7 @@ Public Module ModBase
|
||||
If Not FileName.Contains(":\") Then FileName = $"{Path}PCL\{FileName}.ini"
|
||||
WriteFile(FileName, FileContent.ToString)
|
||||
Catch ex As Exception
|
||||
Log(ex, $"写入文件失败({FileName} -> {Key}:{Value})")
|
||||
Log(ex, $"写入文件失败({FileName} → {Key}:{Value})", LogLevel.Hint)
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
@@ -778,10 +780,10 @@ Public Module ModBase
|
||||
End Try
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 读取文件,如果失败则返回空字符串。
|
||||
''' 读取文件,如果失败则返回空数组。
|
||||
''' </summary>
|
||||
''' <param name="FilePath">文件完整或相对路径。</param>
|
||||
Public Function ReadFile(FilePath As String, Optional Encoding As Encoding = Nothing) As String
|
||||
Public Function ReadFileBytes(FilePath As String, Optional Encoding As Encoding = Nothing) As Byte()
|
||||
Try
|
||||
'还原文件路径
|
||||
If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
|
||||
@@ -791,17 +793,25 @@ Public Module ModBase
|
||||
ReDim FileBytes(ReadStream.Length - 1)
|
||||
ReadStream.Read(FileBytes, 0, ReadStream.Length)
|
||||
End Using
|
||||
ReadFile = If(Encoding Is Nothing, DecodeBytes(FileBytes), Encoding.GetString(FileBytes))
|
||||
Return FileBytes
|
||||
Else
|
||||
Log("[System] 欲读取的文件不存在,已返回空字符串:" & FilePath)
|
||||
Return ""
|
||||
Log("[System] 欲读取的文件不存在,已返回空内容:" & FilePath)
|
||||
Return {}
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Log(ex, "读取文件出错:" & FilePath)
|
||||
Return ""
|
||||
Return {}
|
||||
End Try
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 读取文件,如果失败则返回空字符串。
|
||||
''' </summary>
|
||||
''' <param name="FilePath">文件完整或相对路径。</param>
|
||||
Public Function ReadFile(FilePath As String, Optional Encoding As Encoding = Nothing) As String
|
||||
Dim FileBytes = ReadFileBytes(FilePath)
|
||||
ReadFile = If(Encoding Is Nothing, DecodeBytes(FileBytes), Encoding.GetString(FileBytes))
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 读取流中的所有文本。
|
||||
''' </summary>
|
||||
Public Function ReadFile(Stream As Stream, Optional Encoding As Encoding = Nothing) As String
|
||||
@@ -826,50 +836,39 @@ Public Module ModBase
|
||||
''' <param name="FilePath">文件完整或相对路径。</param>
|
||||
''' <param name="Text">文件内容。</param>
|
||||
''' <param name="Append">是否将文件内容追加到当前文件,而不是覆盖它。</param>
|
||||
Public Function WriteFile(FilePath As String, Text As String, Optional Append As Boolean = False, Optional Encoding As Encoding = Nothing) As Boolean
|
||||
Try
|
||||
'还原文件路径
|
||||
If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
|
||||
'确保目录存在
|
||||
Directory.CreateDirectory(GetPathFromFullPath(FilePath))
|
||||
'写入文件
|
||||
If File.Exists(FilePath) Then
|
||||
'如果文件存在,刷新目前文件
|
||||
Using writer As New StreamWriter(FilePath, Append, If(Encoding, GetEncoding(FilePath)))
|
||||
writer.Write(Text)
|
||||
writer.Flush()
|
||||
writer.Close()
|
||||
End Using
|
||||
Else
|
||||
'如果文件不存在,则新建并写入
|
||||
File.WriteAllText(FilePath, Text, If(Encoding, New UTF8Encoding(False)))
|
||||
End If
|
||||
Return True
|
||||
Catch ex As Exception
|
||||
Log(ex, "写入文件时出错:" & FilePath)
|
||||
Return False
|
||||
End Try
|
||||
End Function
|
||||
Public Sub WriteFile(FilePath As String, Text As String, Optional Append As Boolean = False, Optional Encoding As Encoding = Nothing)
|
||||
'还原文件路径
|
||||
If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
|
||||
'确保目录存在
|
||||
Directory.CreateDirectory(GetPathFromFullPath(FilePath))
|
||||
'写入文件
|
||||
If File.Exists(FilePath) Then
|
||||
'如果文件存在,刷新目前文件
|
||||
Using writer As New StreamWriter(FilePath, Append, If(Encoding, GetEncoding(ReadFileBytes(FilePath))))
|
||||
writer.Write(Text)
|
||||
writer.Flush()
|
||||
writer.Close()
|
||||
End Using
|
||||
Else
|
||||
'如果文件不存在,则新建并写入
|
||||
File.WriteAllText(FilePath, Text, If(Encoding, New UTF8Encoding(False)))
|
||||
End If
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 写入文件。
|
||||
''' 如果 CanThrow 设置为 False,返回是否写入成功。
|
||||
''' </summary>
|
||||
''' <param name="FilePath">文件完整或相对路径。</param>
|
||||
''' <param name="Content">文件内容。</param>
|
||||
''' <param name="Append">是否将文件内容追加到当前文件,而不是覆盖它。</param>
|
||||
Public Function WriteFile(FilePath As String, Content As Byte(), Optional Append As Boolean = False) As Boolean
|
||||
Try
|
||||
'还原文件路径
|
||||
If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
|
||||
'确保目录存在
|
||||
Directory.CreateDirectory(GetPathFromFullPath(FilePath))
|
||||
'写入文件
|
||||
File.WriteAllBytes(FilePath, Content)
|
||||
Return True
|
||||
Catch ex As Exception
|
||||
Log(ex, "写入文件时出错:" & FilePath)
|
||||
Return False
|
||||
End Try
|
||||
End Function
|
||||
Public Sub WriteFile(FilePath As String, Content As Byte(), Optional Append As Boolean = False)
|
||||
'还原文件路径
|
||||
If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
|
||||
'确保目录存在
|
||||
Directory.CreateDirectory(GetPathFromFullPath(FilePath))
|
||||
'写入文件
|
||||
File.WriteAllBytes(FilePath, Content)
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 将流写入文件。
|
||||
''' </summary>
|
||||
@@ -899,21 +898,11 @@ Public Module ModBase
|
||||
|
||||
'文件编码
|
||||
''' <summary>
|
||||
''' 获取文件编码。
|
||||
''' </summary>
|
||||
''' <param name="FilePath">文件完整或相对路径。</param>
|
||||
Public Function GetEncoding(FilePath As String) As Encoding
|
||||
'还原文件路径
|
||||
If Not FilePath.Contains(":\") Then FilePath = Path & FilePath
|
||||
'获取编码
|
||||
GetEncoding = GetEncoding(File.ReadAllBytes(FilePath))
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 获取 Bytes 的编码。
|
||||
''' 根据字节数组分析其编码。
|
||||
''' </summary>
|
||||
Public Function GetEncoding(Bytes As Byte()) As Encoding
|
||||
Dim Length As Integer = Bytes.Count
|
||||
If Length <= 2 Then Return New UTF8Encoding(False) '不带 BOM 的 UTF8
|
||||
If Length < 3 Then Return New UTF8Encoding(False) '不带 BOM 的 UTF8
|
||||
'根据 BOM 判断编码
|
||||
If Bytes(0) >= &HEF Then
|
||||
'有 BOM 类型
|
||||
@@ -986,7 +975,7 @@ Public Module ModBase
|
||||
End Using
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 弹出选取文件对话框并且要求选取文件。
|
||||
''' 弹出选取文件对话框,要求选择一个文件。
|
||||
''' </summary>
|
||||
''' <param name="FileFilter">要求的格式。如:“常用图片文件(*.png;*.jpg)|*.png;*.jpg”。</param>
|
||||
''' <param name="Title">弹窗的标题。</param>
|
||||
@@ -1000,12 +989,32 @@ Public Module ModBase
|
||||
fileDialog.Title = Title
|
||||
fileDialog.ValidateNames = True
|
||||
fileDialog.ShowDialog()
|
||||
SelectFile = fileDialog.FileName
|
||||
Log("[UI] 选择文件返回:" & SelectFile)
|
||||
Log("[UI] 选择单个文件返回:" & fileDialog.FileName)
|
||||
Return fileDialog.FileName
|
||||
End Using
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 弹出选取文件夹对话框并且要求选取文件夹。如果没有选择就返回空字符串。
|
||||
''' 弹出选取文件对话框,要求选择多个文件。
|
||||
''' </summary>
|
||||
''' <param name="FileFilter">要求的格式。如:“常用图片文件(*.png;*.jpg)|*.png;*.jpg”。</param>
|
||||
''' <param name="Title">弹窗的标题。</param>
|
||||
Public Function SelectFiles(FileFilter As String, Title As String) As String()
|
||||
Using fileDialog As New Forms.OpenFileDialog
|
||||
fileDialog.AddExtension = True
|
||||
fileDialog.AutoUpgradeEnabled = True
|
||||
fileDialog.CheckFileExists = True
|
||||
fileDialog.Filter = FileFilter
|
||||
fileDialog.Multiselect = True
|
||||
fileDialog.Title = Title
|
||||
fileDialog.ValidateNames = True
|
||||
fileDialog.ShowDialog()
|
||||
Log("[UI] 选择多个文件返回:" & fileDialog.FileNames.Join(","))
|
||||
Return fileDialog.FileNames
|
||||
End Using
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 弹出选取文件夹对话框,要求选取文件夹。
|
||||
''' 返回以 \ 结尾的完整路径,如果没有选择则返回空字符串。
|
||||
''' </summary>
|
||||
Public Function SelectFolder(Optional Title As String = "选择文件夹") As String
|
||||
Dim folderDialog As New Ookii.Dialogs.Wpf.VistaFolderBrowserDialog With {.ShowNewFolderButton = True, .RootFolder = Environment.SpecialFolder.Desktop, .Description = Title, .UseDescriptionForTitle = True}
|
||||
@@ -1020,7 +1029,7 @@ Public Module ModBase
|
||||
''' </summary>
|
||||
Public Function CheckPermission(Path As String) As Boolean
|
||||
Try
|
||||
If Path = "" Then Return False
|
||||
If String.IsNullOrEmpty(Path) Then Return False
|
||||
If Not Path.EndsWithF("\") Then Path += "\"
|
||||
If Path.EndsWithF(":\System Volume Information\") OrElse Path.EndsWithF(":\$RECYCLE.BIN\") Then Return False
|
||||
If Not Directory.Exists(Path) Then Return False
|
||||
@@ -1112,8 +1121,6 @@ Re:
|
||||
Dim Retry As Boolean = False
|
||||
Re:
|
||||
Try
|
||||
''检测该文件是否在下载中,若在下载则放弃检测
|
||||
'If IgnoreOnDownloading AndAlso NetManage.Files.ContainsKey(FilePath) AndAlso NetManage.Files(FilePath).State <= NetState.Merge Then Return ""
|
||||
'获取 SHA1
|
||||
Dim file As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
|
||||
Dim sha1 As SHA1 = New SHA1CryptoServiceProvider()
|
||||
@@ -1224,7 +1231,8 @@ Re:
|
||||
''' 尝试根据后缀名判断文件种类并解压文件,支持 gz 与 zip,会尝试将 jar 以 zip 方式解压。
|
||||
''' 会尝试创建,但不会清空目标文件夹。
|
||||
''' </summary>
|
||||
Public Sub ExtractFile(CompressFilePath As String, DestDirectory As String, Optional Encode As Encoding = Nothing)
|
||||
Public Sub ExtractFile(CompressFilePath As String, DestDirectory As String, Optional Encode As Encoding = Nothing,
|
||||
Optional ProgressIncrementHandler As Action(Of Double) = Nothing)
|
||||
Directory.CreateDirectory(DestDirectory)
|
||||
If CompressFilePath.EndsWithF(".gz", True) Then
|
||||
'以 gz 方式解压
|
||||
@@ -1240,7 +1248,9 @@ Re:
|
||||
Else
|
||||
'以 zip 方式解压
|
||||
Using Archive = ZipFile.Open(CompressFilePath, ZipArchiveMode.Read, If(Encode, Encoding.GetEncoding("GB18030")))
|
||||
Dim TotalCount As Integer = Archive.Entries.Count
|
||||
For Each Entry As ZipArchiveEntry In Archive.Entries
|
||||
If ProgressIncrementHandler IsNot Nothing Then ProgressIncrementHandler(1 / TotalCount)
|
||||
Dim DestinationPath As String = IO.Path.Combine(DestDirectory, Entry.FullName)
|
||||
If DestinationPath.EndsWithF("\") OrElse DestinationPath.EndsWithF("/") Then
|
||||
Continue For '不创建空文件夹
|
||||
@@ -1259,28 +1269,47 @@ Re:
|
||||
Public Function DeleteDirectory(Path As String, Optional IgnoreIssue As Boolean = False) As Integer
|
||||
If Not Directory.Exists(Path) Then Return 0
|
||||
Dim DeletedCount As Integer = 0
|
||||
Dim Temp As String()
|
||||
Temp = Directory.GetFiles(Path)
|
||||
For Each str As String In Temp
|
||||
Dim Files As String()
|
||||
Try
|
||||
Files = Directory.GetFiles(Path)
|
||||
Catch ex As DirectoryNotFoundException '#4549
|
||||
Log(ex, $"疑似为孤立符号链接,尝试直接删除({Path})", LogLevel.Developer)
|
||||
Directory.Delete(Path)
|
||||
Return 0
|
||||
End Try
|
||||
For Each FilePath As String In Files
|
||||
Dim RetriedFile As Boolean = False
|
||||
RetryFile:
|
||||
Try
|
||||
File.Delete(str)
|
||||
File.Delete(FilePath)
|
||||
DeletedCount += 1
|
||||
Catch ex As Exception
|
||||
If IgnoreIssue Then
|
||||
If Not RetriedFile Then
|
||||
RetriedFile = True
|
||||
Log(ex, $"删除文件失败,将在 0.3s 后重试({FilePath})")
|
||||
Thread.Sleep(300)
|
||||
GoTo RetryFile
|
||||
ElseIf IgnoreIssue Then
|
||||
Log(ex, "删除单个文件可忽略地失败")
|
||||
Else
|
||||
Throw
|
||||
End If
|
||||
End Try
|
||||
Next
|
||||
Temp = Directory.GetDirectories(Path)
|
||||
For Each str As String In Temp
|
||||
For Each str As String In Directory.GetDirectories(Path)
|
||||
DeleteDirectory(str, IgnoreIssue)
|
||||
Next
|
||||
Dim RetriedDir As Boolean = False
|
||||
RetryDir:
|
||||
Try
|
||||
Directory.Delete(Path, True)
|
||||
Catch ex As Exception
|
||||
If IgnoreIssue Then
|
||||
If Not RetriedDir Then
|
||||
RetriedDir = True
|
||||
Log(ex, $"删除文件夹失败,将在 0.3s 后重试({Path})")
|
||||
Thread.Sleep(300)
|
||||
GoTo RetryDir
|
||||
ElseIf IgnoreIssue Then
|
||||
Log(ex, "删除单个文件夹可忽略地失败")
|
||||
Else
|
||||
Throw
|
||||
@@ -1291,13 +1320,16 @@ Re:
|
||||
''' <summary>
|
||||
''' 复制文件夹,失败会抛出异常。
|
||||
''' </summary>
|
||||
Public Sub CopyDirectory(FromPath As String, ToPath As String)
|
||||
Public Sub CopyDirectory(FromPath As String, ToPath As String, Optional ProgressIncrementHandler As Action(Of Double) = Nothing)
|
||||
FromPath = FromPath.Replace("/", "\")
|
||||
If Not FromPath.EndsWithF("\") Then FromPath &= "\"
|
||||
ToPath = ToPath.Replace("/", "\")
|
||||
If Not ToPath.EndsWithF("\") Then ToPath &= "\"
|
||||
For Each File In EnumerateFiles(FromPath)
|
||||
Dim AllFiles = EnumerateFiles(FromPath).ToList
|
||||
Dim FileCount As Integer = AllFiles.Count
|
||||
For Each File In AllFiles
|
||||
CopyFile(File.FullName, File.FullName.Replace(FromPath, ToPath))
|
||||
If ProgressIncrementHandler IsNot Nothing Then ProgressIncrementHandler(1 / FileCount)
|
||||
Next
|
||||
End Sub
|
||||
''' <summary>
|
||||
@@ -1346,8 +1378,8 @@ Re:
|
||||
|
||||
'常见错误(记得同时修改下面的)
|
||||
Dim CommonReason As String = Nothing
|
||||
If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then
|
||||
CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。"
|
||||
If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is BadImageFormatException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then
|
||||
CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。"
|
||||
ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then
|
||||
CommonReason = "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。"
|
||||
ElseIf TypeOf InnerEx Is OutOfMemoryException Then
|
||||
@@ -1366,7 +1398,7 @@ Re:
|
||||
If CommonReason Is Nothing Then
|
||||
Return Desc & Stack & TypeDesc
|
||||
Else
|
||||
Dim Result As String = DescList.First & vbCrLf & CommonReason & vbCrLf & "————————————" & vbCrLf
|
||||
Dim Result As String = CommonReason & vbCrLf & DescList.First & vbCrLf & "————————————" & vbCrLf
|
||||
DescList(0) = "详细错误信息:"
|
||||
Return Result & Join(DescList, vbCrLf & "→ ") & Stack & TypeDesc
|
||||
End If
|
||||
@@ -1394,8 +1426,8 @@ Re:
|
||||
|
||||
'常见错误(记得同时修改上面的)
|
||||
Dim CommonReason As String = Nothing
|
||||
If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then
|
||||
CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。"
|
||||
If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is BadImageFormatException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then
|
||||
CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。"
|
||||
ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then
|
||||
CommonReason = "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。"
|
||||
ElseIf TypeOf InnerEx Is OutOfMemoryException Then
|
||||
@@ -1409,7 +1441,7 @@ Re:
|
||||
|
||||
'构造输出信息
|
||||
If CommonReason IsNot Nothing Then
|
||||
Return DescList.First & ":" & CommonReason
|
||||
Return CommonReason & "详细错误:" & DescList.First
|
||||
Else
|
||||
DescList.Reverse() '让最深层错误在最左边
|
||||
Return Join(DescList, " → ")
|
||||
@@ -1449,14 +1481,14 @@ Re:
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 获取 Json 对象。
|
||||
''' 获取 JSON 对象。
|
||||
''' </summary>
|
||||
Public Function GetJson(Data As String)
|
||||
Try
|
||||
Return JsonConvert.DeserializeObject(Data, New JsonSerializerSettings With {.DateTimeZoneHandling = DateTimeZoneHandling.Local})
|
||||
Catch ex As Exception
|
||||
Dim Length As Integer = If(Data, "").Length
|
||||
Throw New Exception("格式化 json 对象失败:" & If(Length > 10000, Data.Substring(0, 100) & $"...(全长 {Length} 个字符)..." & Right(Data, 100), Data))
|
||||
Throw New Exception("格式化 JSON 失败:" & If(Length > 2000, Data.Substring(0, 500) & $"...(全长 {Length} 个字符)..." & Right(Data, 500), Data))
|
||||
End Try
|
||||
End Function
|
||||
|
||||
@@ -1550,10 +1582,10 @@ Re:
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 获取在子字符串之前的部分。
|
||||
''' 会裁切尽可能多的内容,但如果未找到子字符串则不裁切。
|
||||
''' 获取在子字符串第一次出现之前的部分,例如对 2024/11/08 拆切 / 会得到 2024。
|
||||
''' 如果未找到子字符串则不裁切。
|
||||
''' </summary>
|
||||
<Extension> Public Function Before(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
|
||||
<Extension> Public Function BeforeFirst(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
|
||||
Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.IndexOfF(Text, IgnoreCase))
|
||||
If Pos >= 0 Then
|
||||
Return Str.Substring(0, Pos)
|
||||
@@ -1562,10 +1594,34 @@ Re:
|
||||
End If
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 获取在子字符串之后的部分。
|
||||
''' 会裁切尽可能多的内容,但如果未找到子字符串则不裁切。
|
||||
''' 获取在子字符串最后一次出现之前的部分,例如对 2024/11/08 拆切 / 会得到 2024/11。
|
||||
''' 如果未找到子字符串则不裁切。
|
||||
''' </summary>
|
||||
<Extension> Public Function After(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
|
||||
<Extension> Public Function BeforeLast(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
|
||||
Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.LastIndexOfF(Text, IgnoreCase))
|
||||
If Pos >= 0 Then
|
||||
Return Str.Substring(0, Pos)
|
||||
Else
|
||||
Return Str
|
||||
End If
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 获取在子字符串第一次出现之后的部分,例如对 2024/11/08 拆切 / 会得到 11/08。
|
||||
''' 如果未找到子字符串则不裁切。
|
||||
''' </summary>
|
||||
<Extension> Public Function AfterFirst(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
|
||||
Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.IndexOfF(Text, IgnoreCase))
|
||||
If Pos >= 0 Then
|
||||
Return Str.Substring(Pos + Text.Length)
|
||||
Else
|
||||
Return Str
|
||||
End If
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 获取在子字符串最后一次出现之后的部分,例如对 2024/11/08 拆切 / 会得到 08。
|
||||
''' 如果未找到子字符串则不裁切。
|
||||
''' </summary>
|
||||
<Extension> Public Function AfterLast(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
|
||||
Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.LastIndexOfF(Text, IgnoreCase))
|
||||
If Pos >= 0 Then
|
||||
Return Str.Substring(Pos + Text.Length)
|
||||
@@ -1574,17 +1630,17 @@ Re:
|
||||
End If
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 获取处于两个子字符串之间的部分。
|
||||
''' 会裁切尽可能多的内容:匹配开始使用 LastIndexOf,匹配结束使用 IndexOf,但如果未找到子字符串则不裁切。
|
||||
''' 获取处于两个子字符串之间的部分,裁切尽可能多的内容。
|
||||
''' 如果未找到子字符串则不裁切。
|
||||
''' </summary>
|
||||
<Extension> Public Function Between(Str As String, Before As String, After As String, Optional IgnoreCase As Boolean = False) As String
|
||||
Dim StartPos As Integer = If(String.IsNullOrEmpty(Before), -1, Str.LastIndexOfF(Before, IgnoreCase))
|
||||
<Extension> Public Function Between(Str As String, After As String, Before As String, Optional IgnoreCase As Boolean = False) As String
|
||||
Dim StartPos As Integer = If(String.IsNullOrEmpty(After), -1, Str.LastIndexOfF(After, IgnoreCase))
|
||||
If StartPos >= 0 Then
|
||||
StartPos += Before.Length
|
||||
StartPos += After.Length
|
||||
Else
|
||||
StartPos = 0
|
||||
End If
|
||||
Dim EndPos As Integer = If(String.IsNullOrEmpty(After), -1, Str.IndexOfF(After, StartPos, IgnoreCase))
|
||||
Dim EndPos As Integer = If(String.IsNullOrEmpty(Before), -1, Str.IndexOfF(Before, StartPos, IgnoreCase))
|
||||
If EndPos >= 0 Then
|
||||
Return Str.Substring(StartPos, EndPos - StartPos)
|
||||
ElseIf StartPos > 0 Then
|
||||
@@ -1645,10 +1701,15 @@ Re:
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 输入 And 字符不会报错的 Val。
|
||||
''' 不会报错的 Val。
|
||||
''' 如果输入有误,返回 0。
|
||||
''' </summary>
|
||||
Public Function Val(Str As Object) As Double
|
||||
Return If(TypeOf Str Is String AndAlso Str = "&", 0, Conversion.Val(Str))
|
||||
Try
|
||||
Return If(TypeOf Str Is String AndAlso Str = "&", 0, Conversion.Val(Str))
|
||||
Catch
|
||||
Return 0
|
||||
End Try
|
||||
End Function
|
||||
|
||||
'转义
|
||||
@@ -1656,7 +1717,10 @@ Re:
|
||||
''' 为字符串进行 XML 转义。
|
||||
''' </summary>
|
||||
Public Function EscapeXML(Str As String) As String
|
||||
Return Str.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("'", "'").Replace("""", """).Replace(vbCrLf, "
")
|
||||
If Str.StartsWithF("{") Then Str = "{}" & Str '#4187
|
||||
Return Str.
|
||||
Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("'", "'").
|
||||
Replace("""", """).Replace(vbCrLf, "
")
|
||||
End Function
|
||||
|
||||
'正则
|
||||
@@ -1802,10 +1866,10 @@ Re:
|
||||
'进行搜索,获取相似信息
|
||||
For Each Entry In Entries
|
||||
Entry.Similarity = SearchSimilarityWeighted(Entry.SearchSource, Query)
|
||||
Entry.AbsoluteRight = False
|
||||
For Each Pair In Entry.SearchSource
|
||||
If Pair.Key.Replace(" ", "").ContainsF(Query.Replace(" ", ""), True) Then Entry.AbsoluteRight = True
|
||||
Next
|
||||
Entry.AbsoluteRight =
|
||||
Query.Split(" ").All( '对于按空格分割的每一段
|
||||
Function(QueryPart) Entry.SearchSource.Any( '若与任意一个搜索源完全匹配,则标记为完全匹配项
|
||||
Function(Source) Source.Key.Replace(" ", "").ContainsF(QueryPart, True)))
|
||||
Next
|
||||
'按照相似度进行排序
|
||||
Entries = Sort(Entries,
|
||||
@@ -1834,6 +1898,68 @@ Re:
|
||||
|
||||
#Region "系统"
|
||||
|
||||
''' <summary>
|
||||
''' 线程安全的,可以直接使用 For Each 的 List。
|
||||
''' 在使用 For Each 循环时,列表的结果可能并非最新,但不会抛出异常。
|
||||
''' </summary>
|
||||
Public Class SafeList(Of T)
|
||||
Inherits SynchronizedCollection(Of T)
|
||||
Implements IEnumerable, IEnumerable(Of T)
|
||||
'构造函数
|
||||
Public Sub New()
|
||||
MyBase.New()
|
||||
End Sub
|
||||
Public Sub New(Data As IEnumerable(Of T))
|
||||
MyBase.New(New Object, Data)
|
||||
End Sub
|
||||
Public Shared Widening Operator CType(Data As List(Of T)) As SafeList(Of T)
|
||||
Return New SafeList(Of T)(Data)
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Data As SafeList(Of T)) As List(Of T)
|
||||
Return New List(Of T)(Data)
|
||||
End Operator
|
||||
'基于 SyncLock 覆写
|
||||
Public Overloads Function GetEnumerator() As IEnumerator(Of T) Implements IEnumerable(Of T).GetEnumerator
|
||||
SyncLock SyncRoot
|
||||
Return Items.ToList.GetEnumerator()
|
||||
End SyncLock
|
||||
End Function
|
||||
Private Overloads Function GetEnumeratorGeneral() As IEnumerator Implements IEnumerable.GetEnumerator
|
||||
SyncLock SyncRoot
|
||||
Return Items.ToList.GetEnumerator()
|
||||
End SyncLock
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 可用于临时存放文件的,不含任何特殊字符的文件夹路径,以“\”结尾。
|
||||
''' </summary>
|
||||
Public PathPure As String = GetPureASCIIDir()
|
||||
Private Function GetPureASCIIDir() As String
|
||||
If (Path & "PCL").IsASCII() Then
|
||||
Return Path & "PCL\"
|
||||
ElseIf PathAppdata.IsASCII() Then
|
||||
Return PathAppdata
|
||||
ElseIf PathTemp.IsASCII() Then
|
||||
Return PathTemp
|
||||
Else
|
||||
Return OsDrive & "ProgramData\PCL\"
|
||||
End If
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 指示接取到这个异常的函数进行重试。
|
||||
''' </summary>
|
||||
Public Class RestartException
|
||||
Inherits Exception
|
||||
End Class
|
||||
''' <summary>
|
||||
''' 指示用户手动取消了操作,或用户已知晓操作被取消的原因。
|
||||
''' </summary>
|
||||
Public Class CancelledException
|
||||
Inherits Exception
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 当前程序是否拥有管理员权限。
|
||||
''' </summary>
|
||||
@@ -1852,6 +1978,13 @@ Re:
|
||||
Return NewProcess.ExitCode
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 判断当前系统语言是否为中文。
|
||||
''' </summary>
|
||||
Public Function IsSystemLanguageChinese() As Boolean
|
||||
Return CultureInfo.CurrentCulture.TwoLetterISOLanguageName = "zh" OrElse CultureInfo.CurrentUICulture.TwoLetterISOLanguageName = "zh"
|
||||
End Function
|
||||
|
||||
Private Uuid As Integer = 1
|
||||
Private UuidLock As Object
|
||||
''' <summary>
|
||||
@@ -1943,10 +2076,10 @@ NextElement:
|
||||
'12~60 月,“1 年 2 个月”
|
||||
GetTimeSpanString = Math.Floor(TotalMonthes / 12) & " 年" & If((TotalMonthes Mod 12) > 0, " " & (TotalMonthes Mod 12) & " 个月", "")
|
||||
ElseIf TotalMonthes >= 4 Then
|
||||
'4~11 月,“5 月”
|
||||
GetTimeSpanString = TotalMonthes & " 月"
|
||||
'4~11 月,“5 个月”
|
||||
GetTimeSpanString = TotalMonthes & " 个月"
|
||||
ElseIf TotalMonthes >= 1 Then
|
||||
'1~4 月,“2 月 13 天”
|
||||
'1~4 月,“2 个月 13 天”
|
||||
GetTimeSpanString = TotalMonthes & " 月" & If((Span.Days Mod 30) > 0, " " & (Span.Days Mod 30) & " 天", "")
|
||||
ElseIf Span.TotalDays >= 4 Then
|
||||
'4~30 天,“23 天”
|
||||
@@ -2068,7 +2201,7 @@ NextElement:
|
||||
''' <summary>
|
||||
''' 在新的工作线程中执行代码。
|
||||
''' </summary>
|
||||
Public Function RunInNewThread(Action As Action, Name As String, Optional Priority As ThreadPriority = ThreadPriority.Normal) As Thread
|
||||
Public Function RunInNewThread(Action As Action, Optional Name As String = Nothing, Optional Priority As ThreadPriority = ThreadPriority.Normal) As Thread
|
||||
Dim th As New Thread(
|
||||
Sub()
|
||||
Try
|
||||
@@ -2078,7 +2211,7 @@ NextElement:
|
||||
Catch ex As Exception
|
||||
Log(ex, Name & ":线程执行失败", LogLevel.Feedback)
|
||||
End Try
|
||||
End Sub) With {.Name = Name, .Priority = Priority}
|
||||
End Sub) With {.Name = If(Name, "Runtime New Invoke " & GetUuid() & "#"), .Priority = Priority}
|
||||
th.Start()
|
||||
Return th
|
||||
End Function
|
||||
@@ -2417,6 +2550,14 @@ Retry:
|
||||
Return rect.Contains(bounds.TopLeft) OrElse rect.Contains(bounds.BottomRight)
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 控件是否受到 TextTrimming 属性影响,导致内容被截取。
|
||||
''' </summary>
|
||||
<Extension> Public Function IsTextTrimmed(Control As TextBlock) As Boolean
|
||||
Control.Measure(New Size(Double.MaxValue, Double.MaxValue))
|
||||
Return Control.DesiredSize.Width > Control.ActualWidth
|
||||
End Function
|
||||
|
||||
#End Region
|
||||
|
||||
#Region "Debug"
|
||||
@@ -2657,7 +2798,11 @@ Retry:
|
||||
End Sub
|
||||
Public Function CanFeedback(ShowHint As Boolean) As Boolean
|
||||
If False.Equals(PageSetupSystem.IsLauncherNewest) Then
|
||||
If ShowHint Then MyMsgBox("你的 PCL 不是最新版,因此无法提交反馈。" & vbCrLf & "请先在 设置 → 启动器 中更新启动器,确认该问题在最新版中依然存在,然后再提交反馈。", "无法提交反馈")
|
||||
If ShowHint Then
|
||||
If MyMsgBox($"你的 PCL 不是最新版,因此无法提交反馈。{vbCrLf}请在更新后,确认该问题在最新版中依然存在,然后再提交反馈。", "无法提交反馈", "更新", "取消") = 1 Then
|
||||
UpdateCheckByButton()
|
||||
End If
|
||||
End If
|
||||
Return False
|
||||
Else
|
||||
Return True
|
||||
@@ -2669,7 +2814,7 @@ Retry:
|
||||
Public Sub FeedbackInfo()
|
||||
On Error Resume Next
|
||||
Log("[System] 诊断信息:" & vbCrLf &
|
||||
"操作系统:" & My.Computer.Info.OSFullName & vbCrLf &
|
||||
"操作系统:" & My.Computer.Info.OSFullName & "(32 位:" & Is32BitSystem & ")" & vbCrLf &
|
||||
"剩余内存:" & Int(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024) & " M / " & Int(My.Computer.Info.TotalPhysicalMemory / 1024 / 1024) & " M" & vbCrLf &
|
||||
"DPI:" & DPI & "(" & Math.Round(DPI / 96, 2) * 100 & "%)" & vbCrLf &
|
||||
"MC 文件夹:" & If(PathMcFolder, "Nothing") & vbCrLf &
|
||||
|
||||
@@ -91,17 +91,18 @@
|
||||
_State = value
|
||||
Log("[Loader] 加载器 " & Name & " 状态改变:" & GetStringFromEnum(value))
|
||||
'实现 ILoadingTrigger 接口与 OnStateChanged 回调
|
||||
RunInUi(Sub()
|
||||
Select Case value
|
||||
Case LoadState.Loading
|
||||
LoadingState = MyLoading.MyLoadingState.Run
|
||||
Case LoadState.Failed
|
||||
LoadingState = MyLoading.MyLoadingState.Error
|
||||
Case Else
|
||||
LoadingState = MyLoading.MyLoadingState.Stop
|
||||
End Select
|
||||
RaiseEvent OnStateChangedUi(Me, value, OldState)
|
||||
End Sub)
|
||||
RunInUi(
|
||||
Sub()
|
||||
Select Case value
|
||||
Case LoadState.Loading
|
||||
LoadingState = MyLoading.MyLoadingState.Run
|
||||
Case LoadState.Failed
|
||||
LoadingState = MyLoading.MyLoadingState.Error
|
||||
Case Else
|
||||
LoadingState = MyLoading.MyLoadingState.Stop
|
||||
End Select
|
||||
RaiseEvent OnStateChangedUi(Me, value, OldState)
|
||||
End Sub)
|
||||
If HasOnStateChangedThread Then RunInThread(Sub() RaiseEvent OnStateChangedThread(Me, value, OldState))
|
||||
End Set
|
||||
End Property
|
||||
@@ -287,15 +288,13 @@
|
||||
End SyncLock
|
||||
End Try
|
||||
'检验输入以确定情况
|
||||
If (Not IsForceRestart) AndAlso '不要求强行重启
|
||||
((Input IsNot Nothing AndAlso Input.Equals(Me.Input)) OrElse (Input Is Nothing AndAlso Me.Input Is Nothing)) AndAlso '输入相同
|
||||
(State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已加载成功
|
||||
If IsForceRestart Then Return True '强制要求重启
|
||||
If ((Input Is Nothing) <> (Me.Input Is Nothing)) OrElse (Input IsNot Nothing AndAlso Not Input.Equals(Me.Input)) Then Return True '输入不同
|
||||
If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束
|
||||
(IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse LastFinishedTime = 0 OrElse GetTimeTick() - LastFinishedTime < ReloadTimeout) Then '没有超时
|
||||
'如果输入相同且未出错则不重试
|
||||
Return False
|
||||
Return False '则不重试
|
||||
Else
|
||||
'需要开始
|
||||
Return True
|
||||
Return True '需要开始
|
||||
End If
|
||||
End Function
|
||||
Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
|
||||
@@ -316,7 +315,7 @@
|
||||
Sub()
|
||||
Try
|
||||
IsForceRestarting = IsForceRestart
|
||||
If ModeDebug Then Log("[Loader] 加载线程 " & Name & " (" & Thread.CurrentThread.ManagedThreadId & ") 已启动")
|
||||
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)
|
||||
@@ -440,12 +439,13 @@
|
||||
Exit Sub
|
||||
End If
|
||||
End SyncLock
|
||||
RunInThread(Sub()
|
||||
'中断加载器
|
||||
For Each Loader In Loaders
|
||||
Loader.Abort()
|
||||
Next
|
||||
End Sub)
|
||||
RunInThread(
|
||||
Sub()
|
||||
'中断加载器
|
||||
For Each Loader In Loaders
|
||||
Loader.Abort()
|
||||
Next
|
||||
End Sub)
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
@@ -484,7 +484,7 @@
|
||||
Private Sub Update()
|
||||
If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Aborted Then Exit Sub
|
||||
Dim IsFinished As Boolean = True
|
||||
Dim Ignore As Boolean = False
|
||||
Dim Blocked As Boolean = False
|
||||
Dim Input As Object = Me.Input
|
||||
For Each Loader In Loaders
|
||||
Select Case Loader.State
|
||||
@@ -492,28 +492,30 @@
|
||||
'检查是否需要重启
|
||||
If Loader.GetType.Name.StartsWithF("LoaderTask") Then '类型名后面带有泛型,必须用 StartsWith
|
||||
If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
|
||||
If ModeDebug Then Log("[Loader] 由于输入条件变更,重启已完成的加载器 " & Loader.Name)
|
||||
Log("[Loader] 由于输入条件变更,重启已完成的加载器 " & Loader.Name)
|
||||
GoTo Restart
|
||||
End If
|
||||
'更新下一个加载器的输入
|
||||
Input = CType(Loader, Object).Output
|
||||
End If
|
||||
'如果不让继续启动,且已有加载器正在加载中,就不继续启动
|
||||
If Loader.Block AndAlso Not IsFinished Then Blocked = True
|
||||
Case LoadState.Loading
|
||||
'检查是否需要重启
|
||||
If Loader.GetType.Name.StartsWithF("LoaderTask") Then
|
||||
If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
|
||||
If ModeDebug Then Log("[Loader] 由于输入条件变更,重启进行中的加载器 " & Loader.Name, LogLevel.Developer)
|
||||
Log("[Loader] 由于输入条件变更,重启进行中的加载器 " & Loader.Name, LogLevel.Developer)
|
||||
GoTo Restart
|
||||
End If
|
||||
End If
|
||||
'已经有正在加载中的了,不需要再启动了
|
||||
IsFinished = False
|
||||
Ignore = True
|
||||
Blocked = True
|
||||
Case Else
|
||||
Restart:
|
||||
'未启动,则启动加载器
|
||||
IsFinished = False
|
||||
If Ignore Then Continue For
|
||||
If Blocked Then Continue For
|
||||
If Input IsNot Nothing Then
|
||||
'若输入类型与下一个加载器相同才继续
|
||||
Dim LoaderType As String = Loader.GetType.Name
|
||||
@@ -527,8 +529,8 @@ Restart:
|
||||
Else
|
||||
Loader.Start(IsForceRestart:=IsForceRestarting)
|
||||
End If
|
||||
'如果不让继续启动,那就不启动呗
|
||||
If Loader.Block Then Ignore = True
|
||||
'阻止继续
|
||||
If Loader.Block Then Blocked = True
|
||||
End Select
|
||||
Next
|
||||
If IsFinished Then
|
||||
@@ -566,38 +568,33 @@ Restart:
|
||||
End Class
|
||||
|
||||
'任务栏进度条
|
||||
Public LoaderTaskbarLock As New Object
|
||||
Public LoaderTaskbar As New List(Of LoaderBase)
|
||||
Public LoaderTaskbar As New SafeList(Of LoaderBase)
|
||||
Public LoaderTaskbarProgress As Double = 0 '平滑后的进度
|
||||
Private LoaderTaskbarProgressLast As Shell.TaskbarItemProgressState = Shell.TaskbarItemProgressState.None
|
||||
|
||||
Public Sub LoaderTaskbarAdd(Of T)(Loader As LoaderCombo(Of T))
|
||||
If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRemove(Loader)
|
||||
SyncLock LoaderTaskbarLock
|
||||
LoaderTaskbar.Add(Loader)
|
||||
Log($"[Taskbar] {Loader.Name} 已加入任务列表")
|
||||
End SyncLock
|
||||
LoaderTaskbar.Add(Loader)
|
||||
Log($"[Taskbar] {Loader.Name} 已加入任务列表")
|
||||
End Sub
|
||||
Public Sub LoaderTaskbarProgressRefresh()
|
||||
Try
|
||||
Dim NewState As Shell.TaskbarItemProgressState
|
||||
Dim NewProgress As Double = LoaderTaskbarProgressGet()
|
||||
'检查任务是否完成,若完成则移除
|
||||
SyncLock LoaderTaskbarLock
|
||||
'外显任务是否已经全部完成
|
||||
Dim IsAllDownloadTaskCompleted As Boolean = True
|
||||
For Each Loader In LoaderTaskbar
|
||||
If Loader.State = LoadState.Loading Then IsAllDownloadTaskCompleted = False
|
||||
Next
|
||||
'若单个任务已中止或全部任务已完成,则刷新并移除
|
||||
For Each Task In LoaderTaskbar.ToList()
|
||||
If IsAllDownloadTaskCompleted OrElse Task.State = LoadState.Aborted OrElse Task.State = LoadState.Waiting Then
|
||||
If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRefresh(Task)
|
||||
LoaderTaskbar.Remove(Task)
|
||||
Log($"[Taskbar] {Task.Name} 已移出任务列表")
|
||||
End If
|
||||
Next
|
||||
End SyncLock
|
||||
'外显任务是否已经全部完成
|
||||
Dim IsAllDownloadTaskCompleted As Boolean = True
|
||||
For Each Loader In LoaderTaskbar.ToList()
|
||||
If Loader.State = LoadState.Loading Then IsAllDownloadTaskCompleted = False
|
||||
Next
|
||||
'若单个任务已中止或全部任务已完成,则刷新并移除
|
||||
For Each Task In LoaderTaskbar.ToList()
|
||||
If IsAllDownloadTaskCompleted OrElse Task.State = LoadState.Aborted OrElse Task.State = LoadState.Waiting Then
|
||||
If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRefresh(Task)
|
||||
LoaderTaskbar.Remove(Task)
|
||||
Log($"[Taskbar] {Task.Name} 已移出任务列表")
|
||||
End If
|
||||
Next
|
||||
'更新平滑后的进度
|
||||
If NewProgress <= 0 OrElse NewProgress >= 1 OrElse LoaderTaskbarProgress > NewProgress Then
|
||||
LoaderTaskbarProgress = NewProgress
|
||||
@@ -625,15 +622,8 @@ Restart:
|
||||
End Sub
|
||||
Public Function LoaderTaskbarProgressGet() As Double
|
||||
Try
|
||||
Dim Total As Double = 0, Count As Integer = 0
|
||||
SyncLock LoaderTaskbarLock
|
||||
For Each Task In LoaderTaskbar.ToList
|
||||
Total += Task.Progress
|
||||
Count += 1 '避免多线程影响导致计数出错
|
||||
Next
|
||||
End SyncLock
|
||||
If Count = 0 Then Return 1
|
||||
Return MathClamp(Total / Count, 0, 1)
|
||||
If Not LoaderTaskbar.Any Then Return 1
|
||||
Return MathClamp(LoaderTaskbar.Select(Function(e) e.Progress).Average(), 0, 1)
|
||||
Catch ex As Exception
|
||||
Log(ex, "获取任务栏进度出错", LogLevel.Feedback)
|
||||
Return 0.5
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Imports System.Net
|
||||
Imports PCL.ModNet
|
||||
|
||||
Public Module ModNet
|
||||
Public Const NetDownloadEnd As String = ".PCLDownloading"
|
||||
@@ -141,7 +140,7 @@ Retry:
|
||||
''' </summary>
|
||||
''' <param name="Url">网页的 Url。</param>
|
||||
''' <param name="Encode">网页的编码,通常为 UTF-8。</param>
|
||||
Public Function NetGetCodeByRequestMulty(Url As String, Optional Encode As Encoding = Nothing, Optional Accept As String = "", Optional IsJson As Boolean = False)
|
||||
Public Function NetGetCodeByRequestMultiple(Url As String, Optional Encode As Encoding = Nothing, Optional Accept As String = "", Optional IsJson As Boolean = False)
|
||||
Dim Threads As New List(Of Thread)
|
||||
Dim RequestResult = Nothing
|
||||
Dim RequestEx As Exception = Nothing
|
||||
@@ -207,10 +206,10 @@ RequestFinished:
|
||||
Catch ex As ThreadInterruptedException
|
||||
Throw
|
||||
Catch ex As Exception
|
||||
If ex.GetType.Equals(GetType(WebException)) AndAlso CType(ex, WebException).Status = WebExceptionStatus.Timeout Then
|
||||
Throw New TimeoutException("连接服务器超时,已接收 " & Result.Count & " B(" & Url & ")")
|
||||
If TypeOf ex Is WebException AndAlso CType(ex, WebException).Status = WebExceptionStatus.Timeout Then
|
||||
Throw New TimeoutException($"获取结果失败({CType(ex, WebException).Status},{ex.Message},{Url})", ex)
|
||||
Else
|
||||
Throw New WebException("获取结果失败," & ex.Message & "(" & Url & ")", ex)
|
||||
Throw New WebException($"获取结果失败({If(TypeOf ex Is WebException, CType(ex, WebException).Status & ",", "")}{ex.Message},{Url})", ex)
|
||||
End If
|
||||
Finally
|
||||
Request.Abort()
|
||||
@@ -269,12 +268,7 @@ RequestFinished:
|
||||
'下载
|
||||
Using Client As New WebClient
|
||||
Try
|
||||
If UseBrowserUserAgent Then
|
||||
Client.Headers(HttpRequestHeader.UserAgent) = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36"
|
||||
Else
|
||||
Client.Headers(HttpRequestHeader.UserAgent) = "PCL2/" & VersionStandardCode
|
||||
End If
|
||||
Client.Headers(HttpRequestHeader.Referer) = "http://" & VersionCode & ".pcl2.server/"
|
||||
SecretHeadersSign(Url, Client, UseBrowserUserAgent)
|
||||
Client.DownloadFile(Url, LocalFile)
|
||||
Catch ex As Exception
|
||||
File.Delete(LocalFile)
|
||||
@@ -316,7 +310,7 @@ Retry:
|
||||
Catch ex As ThreadInterruptedException
|
||||
Throw
|
||||
Catch ex As Exception
|
||||
If ex.InnerException IsNot Nothing AndAlso ex.InnerException.Message.Contains("(40") AndAlso DontRetryOnRefused Then RetryCount = 999
|
||||
If ex.InnerException IsNot Nothing AndAlso ex.InnerException.Message.Contains("(40") AndAlso DontRetryOnRefused Then Throw
|
||||
Select Case RetryCount
|
||||
Case 0
|
||||
If ModeDebug Then Log(ex, "[Net] 网络请求第一次失败(" & Url & ")")
|
||||
@@ -335,7 +329,7 @@ Retry:
|
||||
''' <summary>
|
||||
''' 同时发送多个网络请求并要求返回内容。
|
||||
''' </summary>
|
||||
Public Function NetRequestMulty(Url As String, Method As String, Data As Object, ContentType As String, Optional RequestCount As Integer = 4, Optional Headers As Dictionary(Of String, String) = Nothing)
|
||||
Public Function NetRequestMultiple(Url As String, Method As String, Data As Object, ContentType As String, Optional RequestCount As Integer = 4, Optional Headers As Dictionary(Of String, String) = Nothing, Optional MakeLog As Boolean = True)
|
||||
Dim Threads As New List(Of Thread)
|
||||
Dim RequestResult = Nothing
|
||||
Dim RequestEx As Exception = Nothing
|
||||
@@ -344,7 +338,7 @@ Retry:
|
||||
Dim th As New Thread(
|
||||
Sub()
|
||||
Try
|
||||
RequestResult = NetRequestOnce(Url, Method, Data, ContentType, 30000, Headers)
|
||||
RequestResult = NetRequestOnce(Url, Method, Data, ContentType, 30000, Headers, MakeLog)
|
||||
Catch ex As Exception
|
||||
FailCount += 1
|
||||
RequestEx = ex
|
||||
@@ -419,7 +413,7 @@ RequestFinished:
|
||||
Throw
|
||||
Catch ex As WebException
|
||||
If ex.Status = WebExceptionStatus.Timeout Then
|
||||
Throw New WebException("连接服务器超时,请检查你的网络环境是否良好(" & Url & ")", ex)
|
||||
ex = New WebException($"连接服务器超时,请检查你的网络环境是否良好({ex.Message},{Url})", ex)
|
||||
Else
|
||||
'获取请求失败的返回
|
||||
Dim Res As String = ""
|
||||
@@ -433,15 +427,34 @@ RequestFinished:
|
||||
End Using
|
||||
Catch
|
||||
End Try
|
||||
Throw New WebException("网络请求失败(" & Url & "," & ex.Message & ")" & If(String.IsNullOrEmpty(Res), "", vbCrLf & Res), ex)
|
||||
If Res = "" Then
|
||||
ex = New WebException($"网络请求失败({ex.Status},{ex.Message},{Url})", ex)
|
||||
Else
|
||||
ex = New ResponsedWebException($"服务器返回错误({ex.Status},{ex.Message},{Url}){vbCrLf}{Res}", Res, ex)
|
||||
End If
|
||||
End If
|
||||
If MakeLog Then Log(ex, "NetRequestOnce 失败", LogLevel.Developer)
|
||||
Throw ex
|
||||
Catch ex As Exception
|
||||
Throw New WebException("网络请求失败(" & Url & ")", ex)
|
||||
ex = New WebException("网络请求失败(" & Url & ")", ex)
|
||||
If MakeLog Then Log(ex, "NetRequestOnce 失败", LogLevel.Developer)
|
||||
Throw ex
|
||||
Finally
|
||||
If DataStream IsNot Nothing Then DataStream.Dispose()
|
||||
If Resp IsNot Nothing Then Resp.Dispose()
|
||||
End Try
|
||||
End Function
|
||||
Public Class ResponsedWebException
|
||||
Inherits WebException
|
||||
''' <summary>
|
||||
''' 远程服务器给予的回复。
|
||||
''' </summary>
|
||||
Public Overloads Property Response As String
|
||||
Public Sub New(Message As String, Response As String, InnerException As Exception)
|
||||
MyBase.New(Message, InnerException)
|
||||
Me.Response = Response
|
||||
End Sub
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 最大线程数。
|
||||
@@ -688,8 +701,7 @@ RequestFinished:
|
||||
''' <summary>
|
||||
''' 所属的文件列表任务。
|
||||
''' </summary>
|
||||
Public Tasks As New List(Of LoaderDownload)
|
||||
|
||||
Public Tasks As New SafeList(Of LoaderDownload)
|
||||
''' <summary>
|
||||
''' 所有下载源。
|
||||
''' </summary>
|
||||
@@ -881,7 +893,6 @@ RequestFinished:
|
||||
Public ReadOnly LockState As New Object
|
||||
Public ReadOnly LockChain As New Object
|
||||
Public ReadOnly LockSource As New Object
|
||||
Public ReadOnly LockTasks As New Object
|
||||
|
||||
Public ReadOnly Uuid As Integer = GetUuid()
|
||||
Public Overrides Function Equals(obj As Object) As Boolean
|
||||
@@ -919,7 +930,7 @@ RequestFinished:
|
||||
|
||||
'条件检测
|
||||
If NetTaskThreadCount >= NetTaskThreadLimit OrElse IsSourceFailed() OrElse
|
||||
(IsNoSplit AndAlso Threads IsNot Nothing AndAlso Threads.State <> NetState.Error) Then Return Nothing
|
||||
(IsNoSplit AndAlso Threads IsNot Nothing AndAlso Threads.State <> NetState.Error) Then Return Nothing
|
||||
If State >= NetState.Merge OrElse State = NetState.WaitForCheck Then Return Nothing
|
||||
SyncLock LockState
|
||||
If State < NetState.Connect Then State = NetState.Connect
|
||||
@@ -928,7 +939,6 @@ RequestFinished:
|
||||
Dim StartPosition As Long, StartSource As NetSource = Nothing
|
||||
Dim Th As Thread, ThreadInfo As NetThread
|
||||
SyncLock LockChain
|
||||
|
||||
'获取线程起点与下载源
|
||||
'不分割
|
||||
If IsNoSplit Then GoTo Capture
|
||||
@@ -964,7 +974,8 @@ Capture:
|
||||
Next
|
||||
'是否禁用多线程,以及规定碎片大小
|
||||
Dim TargetUrl As String = GetSource().Url
|
||||
If TargetUrl.Contains("pcl2-server") OrElse TargetUrl.Contains("gitcode.net") OrElse TargetUrl.Contains("github.com") OrElse TargetUrl.Contains("modrinth.com") Then Return Nothing
|
||||
If TargetUrl.Contains("pcl2-server") OrElse TargetUrl.Contains("mcimirror") OrElse TargetUrl.Contains("github.com") OrElse
|
||||
TargetUrl.Contains("optifine.net") OrElse TargetUrl.Contains("modrinth") Then Return Nothing
|
||||
'寻找最大碎片
|
||||
'FUTURE: 下载引擎重做,计算下载源平均链接时间和线程下载速度,按最高时间节省来开启多线程
|
||||
Dim FilePieceMax As NetThread = Threads
|
||||
@@ -980,10 +991,8 @@ StartThread:
|
||||
If (StartPosition > FileSize AndAlso FileSize >= 0 AndAlso Not IsUnknownSize) OrElse StartPosition < 0 OrElse IsNothing(StartSource) Then Return Nothing
|
||||
'构建线程
|
||||
Dim ThreadUuid As Integer = GetUuid()
|
||||
SyncLock LockTasks
|
||||
If Not Tasks.Any() Then Return Nothing '由于中断,已没有可用任务
|
||||
Th = New Thread(AddressOf Thread) With {.Name = $"NetTask {Tasks(0).Uuid}/{Uuid} Download {ThreadUuid}#", .Priority = ThreadPriority.BelowNormal}
|
||||
End SyncLock
|
||||
If Not Tasks.Any() Then Return Nothing '由于中断,已没有可用任务
|
||||
Th = New Thread(AddressOf Thread) With {.Name = $"NetTask {Tasks(0).Uuid}/{Uuid} Download {ThreadUuid}#", .Priority = ThreadPriority.BelowNormal}
|
||||
ThreadInfo = New NetThread With {.Uuid = ThreadUuid, .DownloadStart = StartPosition, .Thread = Th, .Source = StartSource, .Task = Me, .State = NetState.WaitForDownload}
|
||||
'链表处理
|
||||
If ThreadInfo.IsFirstThread OrElse Threads Is Nothing Then
|
||||
@@ -1040,6 +1049,15 @@ StartThread:
|
||||
If ModeDebug AndAlso HttpResponse.ResponseUri.OriginalString <> Info.Source.Url Then
|
||||
Log($"[Download] {LocalName} {Info.Uuid}#:重定向至 {HttpResponse.ResponseUri.OriginalString}")
|
||||
End If
|
||||
''从响应头获取文件名
|
||||
'If Info.IsFirstThread Then
|
||||
' Dim FileName As String = GetFileNameFromResponse(HttpResponse)
|
||||
' If ModeDebug Then Log($"[Download] {LocalName} {Info.Uuid}#:远程文件名:{If(FileName, "未提供")}")
|
||||
' If FileName IsNot Nothing AndAlso LocalName = "待定" Then
|
||||
' LocalName = FileName
|
||||
' Log($"[Download] {LocalName} {Info.Uuid}#:从响应头获取到文件名")
|
||||
' End If
|
||||
'End If
|
||||
'文件大小校验
|
||||
ContentLength = HttpResponse.ContentLength
|
||||
If ContentLength = -1 Then
|
||||
@@ -1124,7 +1142,7 @@ NotSupportRange:
|
||||
If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount
|
||||
End SyncLock
|
||||
Dim DeltaTime = GetTimeTick() - Info.LastReceiveTime
|
||||
If DeltaTime > 1000000 Then DeltaTime = 0 '时间刻反转导致出现极大值
|
||||
If DeltaTime > 1000000 Then DeltaTime = 1 '时间刻反转导致出现极大值
|
||||
If RealDataCount > 0 Then
|
||||
'有数据
|
||||
If Info.DownloadDone = 0 Then
|
||||
@@ -1140,11 +1158,9 @@ NotSupportRange:
|
||||
End If
|
||||
SyncLock LockCount
|
||||
Info.Source.FailCount = 0
|
||||
SyncLock LockTasks
|
||||
For Each Task In Tasks
|
||||
Task.FailCount = 0
|
||||
Next
|
||||
End SyncLock
|
||||
For Each Task In Tasks
|
||||
Task.FailCount = 0
|
||||
Next
|
||||
End SyncLock
|
||||
NetManager.DownloadDone += RealDataCount
|
||||
SyncLock LockDone
|
||||
@@ -1167,7 +1183,7 @@ NotSupportRange:
|
||||
ResultStream.Write(HttpData, 0, RealDataCount)
|
||||
End If
|
||||
'检查速度是否过慢
|
||||
If DeltaTime > 1000 AndAlso DeltaTime > RealDataCount Then '数据包间隔大于 1s,且速度小于 1K/s
|
||||
If DeltaTime > 1500 AndAlso DeltaTime > RealDataCount Then '数据包间隔大于 1.5s,且速度小于 1.5K/s
|
||||
Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。")
|
||||
End If
|
||||
Info.LastReceiveTime = GetTimeTick()
|
||||
@@ -1198,11 +1214,9 @@ SourceBreak:
|
||||
'状态变更
|
||||
SyncLock LockCount
|
||||
Info.Source.FailCount += 1
|
||||
SyncLock LockTasks
|
||||
For Each Task In Tasks
|
||||
Task.FailCount += 1
|
||||
Next
|
||||
End SyncLock
|
||||
For Each Task In Tasks
|
||||
Task.FailCount += 1
|
||||
Next
|
||||
End SyncLock
|
||||
Dim IsTimeoutString As String = GetExceptionSummary(ex).ToLower.Replace(" ", "")
|
||||
Dim IsTimeout As Boolean = IsTimeoutString.Contains("由于连接方在一段时间后没有正确答复或连接的主机没有反应") OrElse
|
||||
@@ -1281,6 +1295,17 @@ Wrong:
|
||||
If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso State < NetState.Merge Then Merge()
|
||||
End Try
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 从 HTTP 响应头中获取文件名。
|
||||
''' 如果没有,返回 Nothing。
|
||||
''' </summary>
|
||||
Private Function GetFileNameFromResponse(response As HttpWebResponse) As String
|
||||
Dim header As String = response.Headers("Content-Disposition")
|
||||
If String.IsNullOrEmpty(header) Then Return Nothing
|
||||
'attachment; filename="filename.ext"
|
||||
If Not header.Contains("filename=") Then Return Nothing
|
||||
Return header.AfterLast("filename=").Trim(""""c, " "c).BeforeFirst(";")
|
||||
End Function
|
||||
|
||||
'下载文件的最终收束事件
|
||||
''' <summary>
|
||||
@@ -1307,7 +1332,7 @@ Retry:
|
||||
'合并文件
|
||||
If IsNoSplit Then
|
||||
'仅有一个线程,从缓存中输出
|
||||
If ModeDebug Then Log($"[Download] {LocalName}:下载结束,从缓存输出文件")
|
||||
If ModeDebug Then Log($"[Download] {LocalName}:下载结束,从缓存输出文件,长度:" & SmailFileCache.Count)
|
||||
MergeFile = New FileStream(LocalPath, FileMode.Create)
|
||||
AddWriter = New BinaryWriter(MergeFile)
|
||||
AddWriter.Write(SmailFileCache.ToArray)
|
||||
@@ -1338,13 +1363,13 @@ Retry:
|
||||
If Check.ActualSize = -1 Then
|
||||
Check.ActualSize = FileSize
|
||||
ElseIf Check.ActualSize <> FileSize Then
|
||||
Throw New Exception("文件大小不一致:任务要求为 " & Check.ActualSize & " B,网络获取结果为 " & FileSize & "B")
|
||||
Throw New Exception($"文件大小不一致:任务要求为 {Check.ActualSize} B,网络获取结果为 {FileSize}B")
|
||||
End If
|
||||
End If
|
||||
'检查文件
|
||||
Dim CheckResult As String = Check?.Check(LocalPath)
|
||||
If CheckResult IsNot Nothing Then
|
||||
Log($"[Download] {LocalName} 合并文件失败,下载线程细节:")
|
||||
Log($"[Download] {LocalName} 文件校验失败,下载线程细节:")
|
||||
For Each Th As NetThread In Threads
|
||||
Log($"[Download] {Th.Uuid}#,状态 {GetStringFromEnum(Th.State)},范围 {Th.DownloadStart}~{Th.DownloadStart + Th.DownloadDone},完成 {Th.DownloadDone},剩余 {Th.DownloadUndone}")
|
||||
Next
|
||||
@@ -1397,10 +1422,8 @@ Retry:
|
||||
''' </summary>
|
||||
Public Sub Abort(CausedByTask As LoaderDownload)
|
||||
'从特定任务中移除,如果它还属于其他任务,则继续下载
|
||||
SyncLock LockTasks
|
||||
Tasks.Remove(CausedByTask)
|
||||
If Tasks.Any Then Exit Sub
|
||||
End SyncLock
|
||||
Tasks.Remove(CausedByTask)
|
||||
If Tasks.Any Then Exit Sub
|
||||
'确认中断
|
||||
SyncLock LockState
|
||||
If State >= NetState.Finish Then Exit Sub
|
||||
@@ -1430,11 +1453,9 @@ Retry:
|
||||
NetManager.FileRemain -= 1
|
||||
If PrintLog Then Log("[Download] " & LocalName & ":已完成,剩余文件 " & NetManager.FileRemain)
|
||||
End SyncLock
|
||||
SyncLock LockTasks
|
||||
For Each Task In Tasks
|
||||
Task.OnFileFinish(Me)
|
||||
Next
|
||||
End SyncLock
|
||||
For Each Task In Tasks
|
||||
Task.OnFileFinish(Me)
|
||||
Next
|
||||
End Sub
|
||||
|
||||
End Class
|
||||
@@ -1449,8 +1470,7 @@ Retry:
|
||||
''' <summary>
|
||||
''' 需要下载的文件。
|
||||
''' </summary>
|
||||
Public Files As List(Of NetFile)
|
||||
Private ReadOnly FilesLock As New Object
|
||||
Public Files As SafeList(Of NetFile)
|
||||
''' <summary>
|
||||
''' 剩余未完成的文件数。(用于减轻 FilesLock 的占用)
|
||||
''' </summary>
|
||||
@@ -1463,9 +1483,7 @@ Retry:
|
||||
Public Overrides Property Progress As Double
|
||||
Get
|
||||
If State >= LoadState.Finished Then Return 1
|
||||
SyncLock FilesLock
|
||||
If Not Files.Any() Then Return 0 '必须返回 0,否则在获取列表的时候会错觉已经下载完了
|
||||
End SyncLock
|
||||
If Not Files.Any() Then Return 0 '必须返回 0,否则在获取列表的时候会错觉已经下载完了
|
||||
Return _Progress
|
||||
End Get
|
||||
Set(value As Double)
|
||||
@@ -1487,16 +1505,14 @@ Retry:
|
||||
Log("[Download] 由于同加载器中失败次数过多引发强制失败:连续失败了 " & value & " 次", LogLevel.Debug)
|
||||
On Error Resume Next
|
||||
Dim ExList As New List(Of Exception)
|
||||
SyncLock FilesLock
|
||||
For Each File In Files
|
||||
For Each Source In File.Sources
|
||||
If Source.Ex IsNot Nothing Then
|
||||
ExList.Add(Source.Ex)
|
||||
If ExList.Count > 10 Then GoTo FinishExCatch
|
||||
End If
|
||||
Next
|
||||
For Each File In Files
|
||||
For Each Source In File.Sources
|
||||
If Source.Ex IsNot Nothing Then
|
||||
ExList.Add(Source.Ex)
|
||||
If ExList.Count > 10 Then GoTo FinishExCatch
|
||||
End If
|
||||
Next
|
||||
End SyncLock
|
||||
Next
|
||||
FinishExCatch:
|
||||
OnFail(ExList)
|
||||
End If
|
||||
@@ -1513,17 +1529,15 @@ FinishExCatch:
|
||||
'计算进度
|
||||
Dim NewProgress As Double = 0
|
||||
Dim TotalProgress As Double = 0
|
||||
SyncLock FilesLock
|
||||
For Each File In Files
|
||||
If File.IsCopy Then
|
||||
NewProgress += File.Progress * 0.2
|
||||
TotalProgress += 0.2
|
||||
Else
|
||||
NewProgress += File.Progress
|
||||
TotalProgress += 1
|
||||
End If
|
||||
Next
|
||||
End SyncLock
|
||||
For Each File In Files
|
||||
If File.IsCopy Then
|
||||
NewProgress += File.Progress * 0.2
|
||||
TotalProgress += 0.2
|
||||
Else
|
||||
NewProgress += File.Progress
|
||||
TotalProgress += 1
|
||||
End If
|
||||
Next
|
||||
If TotalProgress > 0 Then NewProgress /= TotalProgress
|
||||
'刷新进度
|
||||
If NewProgress < 1 AndAlso NewProgress > 0 Then NewProgress = 2 * (NewProgress ^ 3) - 3 * (NewProgress ^ 2) + 2 * NewProgress '2x^3-3x^2+2x,模拟开头和结尾更慢的情况
|
||||
@@ -1532,27 +1546,25 @@ FinishExCatch:
|
||||
|
||||
Public Sub New(Name As String, FileTasks As List(Of NetFile))
|
||||
Me.Name = Name
|
||||
Files = FileTasks
|
||||
Files = New SafeList(Of NetFile)(FileTasks)
|
||||
End Sub
|
||||
Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
|
||||
SyncLock FilesLock
|
||||
If Input IsNot Nothing Then Files = Input
|
||||
'去重
|
||||
Dim ResultArray As New List(Of NetFile)
|
||||
For i = 0 To Files.Count - 1
|
||||
For ii = i + 1 To Files.Count - 1
|
||||
If Files(i).LocalPath = Files(ii).LocalPath Then GoTo NextElement
|
||||
Next
|
||||
ResultArray.Add(Files(i))
|
||||
If Input IsNot Nothing Then Files = New SafeList(Of NetFile)(Input)
|
||||
'去重
|
||||
Dim ResultArray As New SafeList(Of NetFile)
|
||||
For i = 0 To Files.Count - 1
|
||||
For ii = i + 1 To Files.Count - 1
|
||||
If Files(i).LocalPath = Files(ii).LocalPath Then GoTo NextElement
|
||||
Next
|
||||
ResultArray.Add(Files(i))
|
||||
NextElement:
|
||||
Next i
|
||||
Files = ResultArray
|
||||
'设置剩余文件数
|
||||
SyncLock FileRemainLock
|
||||
For Each File In Files
|
||||
If File.State <> NetState.Finish Then FileRemain += 1
|
||||
Next
|
||||
End SyncLock
|
||||
Next
|
||||
Files = ResultArray
|
||||
'设置剩余文件数
|
||||
SyncLock FileRemainLock
|
||||
For Each File In Files
|
||||
If File.State <> NetState.Finish Then FileRemain += 1
|
||||
Next
|
||||
End SyncLock
|
||||
State = LoadState.Loading
|
||||
'开始执行
|
||||
@@ -1595,25 +1607,23 @@ NextElement:
|
||||
Next
|
||||
End If
|
||||
'最多 5 个线程,最少每个线程分配 10 个文件
|
||||
SyncLock FilesLock
|
||||
Dim FilesPerThread As Integer = Math.Max(5, Files.Count / 10 + 1)
|
||||
Dim FilesInThread As New List(Of NetFile)
|
||||
For Each File In Files
|
||||
FilesInThread.Add(File)
|
||||
If FilesInThread.Count = FilesPerThread Then
|
||||
Dim FilesToRun As New List(Of NetFile)
|
||||
FilesToRun.AddRange(FilesInThread)
|
||||
RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "NetTask FileCopy " & Uuid)
|
||||
FilesInThread.Clear()
|
||||
End If
|
||||
Next
|
||||
If FilesInThread.Any Then
|
||||
Dim FilesPerThread As Integer = Math.Max(5, Files.Count / 10 + 1)
|
||||
Dim FilesInThread As New List(Of NetFile)
|
||||
For Each File In Files
|
||||
FilesInThread.Add(File)
|
||||
If FilesInThread.Count = FilesPerThread Then
|
||||
Dim FilesToRun As New List(Of NetFile)
|
||||
FilesToRun.AddRange(FilesInThread)
|
||||
RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "NetTask FileCopy " & Uuid)
|
||||
FilesInThread.Clear()
|
||||
End If
|
||||
End SyncLock
|
||||
Next
|
||||
If FilesInThread.Any Then
|
||||
Dim FilesToRun As New List(Of NetFile)
|
||||
FilesToRun.AddRange(FilesInThread)
|
||||
RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "NetTask FileCopy " & Uuid)
|
||||
FilesInThread.Clear()
|
||||
End If
|
||||
Catch ex As Exception
|
||||
OnFail(New List(Of Exception) From {ex})
|
||||
End Try
|
||||
@@ -1652,6 +1662,9 @@ NextElement:
|
||||
'复制已存在的文件
|
||||
For Each FileToken In ExistFiles
|
||||
Dim File As NetFile = FileToken.Key
|
||||
SyncLock LockState
|
||||
If File.State > NetState.WaitForCopy Then Exit Sub
|
||||
End SyncLock
|
||||
Dim LocalPath As String = FileToken.Value
|
||||
Dim RetryCount As Integer = 0
|
||||
Retry:
|
||||
@@ -1705,24 +1718,20 @@ Retry:
|
||||
Dim UsefulExs = ExList.Where(Function(e) Not e.Message.Contains("(404)")).ToList
|
||||
[Error] = If(UsefulExs.Any, UsefulExs(0), ExList(0))
|
||||
'获取实际失败的文件
|
||||
SyncLock FilesLock
|
||||
For Each File In Files
|
||||
If File.State = NetState.Error Then
|
||||
[Error] = New Exception("文件下载失败:" & File.LocalPath & vbCrLf & Join(
|
||||
File.Sources.Select(Function(s) If(s.Ex Is Nothing, s.Url, s.Ex.Message & "(" & s.Url & ")")), vbCrLf), [Error])
|
||||
Exit For
|
||||
End If
|
||||
Next
|
||||
End SyncLock
|
||||
For Each File In Files
|
||||
If File.State = NetState.Error Then
|
||||
[Error] = New Exception("文件下载失败:" & File.LocalPath & vbCrLf & Join(
|
||||
File.Sources.Select(Function(s) If(s.Ex Is Nothing, s.Url, s.Ex.Message & "(" & s.Url & ")")), vbCrLf), [Error])
|
||||
Exit For
|
||||
End If
|
||||
Next
|
||||
'在设置 Error 对象后再更改为失败,避免 WaitForExit 无法捕获错误
|
||||
State = LoadState.Failed
|
||||
End SyncLock
|
||||
'中断所有文件
|
||||
SyncLock FilesLock
|
||||
For Each TaskFile In Files
|
||||
If TaskFile.State < NetState.Merge Then TaskFile.State = NetState.Error
|
||||
Next
|
||||
End SyncLock
|
||||
For Each TaskFile In Files
|
||||
If TaskFile.State < NetState.Merge Then TaskFile.State = NetState.Error
|
||||
Next
|
||||
'在退出同步锁后再进行日志输出
|
||||
Dim ErrOutput As New List(Of String)
|
||||
For Each Ex As Exception In ExList
|
||||
@@ -1737,11 +1746,9 @@ Retry:
|
||||
End SyncLock
|
||||
Log("[Download] " & Name & " 已取消!")
|
||||
'中断所有文件
|
||||
SyncLock FilesLock
|
||||
For Each TaskFile In Files
|
||||
TaskFile.Abort(Me)
|
||||
Next
|
||||
End SyncLock
|
||||
For Each TaskFile In Files
|
||||
TaskFile.Abort(Me)
|
||||
Next
|
||||
End Sub
|
||||
|
||||
End Class
|
||||
@@ -1763,8 +1770,7 @@ Retry:
|
||||
''' <summary>
|
||||
''' 当前的所有下载任务。
|
||||
''' </summary>
|
||||
Public Tasks As New List(Of LoaderDownload)
|
||||
Private ReadOnly LockTasks As New Object
|
||||
Public Tasks As New SafeList(Of LoaderDownload)
|
||||
|
||||
''' <summary>
|
||||
''' 已下载完成的大小。
|
||||
@@ -1837,11 +1843,9 @@ Retry:
|
||||
End If
|
||||
#End Region
|
||||
#Region "刷新下载任务属性"
|
||||
SyncLock LockTasks
|
||||
For Each Task In Tasks
|
||||
Task.RefreshStat()
|
||||
Next
|
||||
End SyncLock
|
||||
For Each Task In Tasks
|
||||
Task.RefreshStat()
|
||||
Next
|
||||
#End Region
|
||||
Catch ex As Exception
|
||||
Log(ex, "刷新下载公开属性失败")
|
||||
@@ -1880,7 +1884,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(40) '减少 BMCLAPI 请求频率,避免 Too Many Requests
|
||||
If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(70) '减少 BMCLAPI 请求频率,避免 Too Many Requests
|
||||
Next
|
||||
'为进行中的文件追加线程
|
||||
If Speed >= NetTaskSpeedLimitLow Then Continue While '下载速度足够,无需新增
|
||||
@@ -1900,7 +1904,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(40) '减少 BMCLAPI 请求频率,避免 Too Many Requests
|
||||
If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(70) '减少 BMCLAPI 请求频率,避免 Too Many Requests
|
||||
Next
|
||||
End While
|
||||
Catch ex As Exception
|
||||
@@ -1961,9 +1965,7 @@ Retry:
|
||||
If Files(File.LocalPath).State >= NetState.Finish Then
|
||||
'该文件已经下载过一次,且下载完成
|
||||
'将已下载的文件替换成当前文件,重新下载
|
||||
SyncLock File.LockTasks
|
||||
File.Tasks.Add(Task)
|
||||
End SyncLock
|
||||
File.Tasks.Add(Task)
|
||||
Files(File.LocalPath) = File
|
||||
SyncLock LockRemain
|
||||
FileRemain += 1
|
||||
@@ -1974,15 +1976,11 @@ Retry:
|
||||
'该文件正在下载中
|
||||
'将当前文件替换成下载中的文件,即两个任务指向同一个文件
|
||||
File = Files(File.LocalPath)
|
||||
SyncLock File.LockTasks
|
||||
File.Tasks.Add(Task)
|
||||
End SyncLock
|
||||
File.Tasks.Add(Task)
|
||||
End If
|
||||
Else
|
||||
'没有该文件
|
||||
SyncLock File.LockTasks
|
||||
File.Tasks.Add(Task)
|
||||
End SyncLock
|
||||
File.Tasks.Add(Task)
|
||||
Files.Add(File.LocalPath, File)
|
||||
SyncLock LockRemain
|
||||
FileRemain += 1
|
||||
@@ -1993,9 +1991,7 @@ Retry:
|
||||
Task.Files(i) = File '回设
|
||||
Next
|
||||
End SyncLock
|
||||
SyncLock LockTasks
|
||||
Tasks.Add(Task)
|
||||
End SyncLock
|
||||
Tasks.Add(Task)
|
||||
End Sub
|
||||
|
||||
End Class
|
||||
@@ -2004,14 +2000,12 @@ Retry:
|
||||
''' 是否有正在进行中、需要在下载管理页面显示的下载任务?
|
||||
''' </summary>
|
||||
Public Function HasDownloadingTask(Optional IgnoreCustomDownload As Boolean = False) As Boolean
|
||||
SyncLock LoaderTaskbarLock
|
||||
For Each Task In LoaderTaskbar
|
||||
If (Task.Show AndAlso Task.State = LoadState.Loading) AndAlso
|
||||
(Not IgnoreCustomDownload OrElse Not Task.Name.ToString.Contains("自定义下载")) Then
|
||||
Return True
|
||||
End If
|
||||
Next
|
||||
End SyncLock
|
||||
For Each Task In LoaderTaskbar.ToList()
|
||||
If (Task.Show AndAlso Task.State = LoadState.Loading) AndAlso
|
||||
(Not IgnoreCustomDownload OrElse Not Task.Name.ToString.Contains("自定义下载")) Then
|
||||
Return True
|
||||
End If
|
||||
Next
|
||||
Return False
|
||||
End Function
|
||||
|
||||
|
||||
@@ -233,6 +233,8 @@ Public Class ValidateFolderName
|
||||
'检查特殊字符串
|
||||
Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!", True).Validate(Str)
|
||||
If Not InvalidStrCheck = "" Then Return InvalidStrCheck
|
||||
'检查 NTFS 8.3 文件名(#4505)
|
||||
If RegexCheck(Str, ".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
|
||||
'检查文件夹重名
|
||||
Dim Arr As New List(Of String)
|
||||
If PathIgnore IsNot Nothing Then
|
||||
@@ -289,6 +291,8 @@ Public Class ValidateFileName
|
||||
'检查特殊字符串
|
||||
Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件名不可为 %!", True).Validate(Str)
|
||||
If Not InvalidStrCheck = "" Then Return InvalidStrCheck
|
||||
'检查 NTFS 8.3 文件名(#4505)
|
||||
If RegexCheck(Str, ".{2,}~\d") Then Return "文件名不能包含这一特殊格式!"
|
||||
'检查文件重名
|
||||
If ParentFolder IsNot Nothing Then
|
||||
Dim DirInfo As New DirectoryInfo(ParentFolder)
|
||||
@@ -355,6 +359,8 @@ Fin:
|
||||
'检查特殊字符串
|
||||
Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!").Validate(SubStr)
|
||||
If Not InvalidStrCheck = "" Then Return InvalidStrCheck
|
||||
'检查 NTFS 8.3 文件名(#4505)
|
||||
If RegexCheck(Str, ".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
|
||||
Next
|
||||
Return ""
|
||||
End Function
|
||||
|
||||
@@ -7,7 +7,7 @@ Public Class MyBitmap
|
||||
''' <summary>
|
||||
''' 位图缓存。
|
||||
''' </summary>
|
||||
Public Shared BitmapCache As New Dictionary(Of String, MyBitmap)
|
||||
Public Shared BitmapCache As New Concurrent.ConcurrentDictionary(Of String, MyBitmap)
|
||||
|
||||
''' <summary>
|
||||
''' 存储的图片
|
||||
@@ -17,15 +17,19 @@ Public Class MyBitmap
|
||||
'自动类型转换
|
||||
'支持的类:Image,ImageSource,Bitmap,ImageBrush,BitmapSource
|
||||
Public Shared Widening Operator CType(Image As System.Drawing.Image) As MyBitmap
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return New MyBitmap(Image)
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As MyBitmap) As System.Drawing.Image
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return Image.Pic
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As ImageSource) As MyBitmap
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return New MyBitmap(Image)
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As MyBitmap) As ImageSource
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Dim Bitmap = Image.Pic
|
||||
Dim rect = New System.Drawing.Rectangle(0, 0, Bitmap.Width, Bitmap.Height)
|
||||
Dim bitmapData = Bitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)
|
||||
@@ -37,15 +41,19 @@ Public Class MyBitmap
|
||||
End Try
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As System.Drawing.Bitmap) As MyBitmap
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return New MyBitmap(Image)
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As MyBitmap) As System.Drawing.Bitmap
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return Image.Pic
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As ImageBrush) As MyBitmap
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return New MyBitmap(Image)
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As MyBitmap) As ImageBrush
|
||||
If Image Is Nothing Then Return Nothing
|
||||
Return New ImageBrush(New MyBitmap(Image.Pic))
|
||||
End Operator
|
||||
|
||||
@@ -61,23 +69,32 @@ Public Class MyBitmap
|
||||
Pic = BitmapCache(FilePathOrResourceName).Pic
|
||||
Else
|
||||
Pic = New MyBitmap(CType((New ImageSourceConverter).ConvertFromString(FilePathOrResourceName), ImageSource))
|
||||
BitmapCache.Add(FilePathOrResourceName, Pic)
|
||||
BitmapCache.TryAdd(FilePathOrResourceName, Pic)
|
||||
End If
|
||||
Else
|
||||
'使用这种自己接管 FileStream 的方法加载才能解除文件占用
|
||||
Using InputStream As New FileStream(FilePathOrResourceName, FileMode.Open)
|
||||
Pic = New System.Drawing.Bitmap(InputStream)
|
||||
InputStream.Dispose()
|
||||
'判断是否为 WebP 文件头
|
||||
Dim Header(1) As Byte
|
||||
InputStream.Read(Header, 0, 2)
|
||||
InputStream.Seek(0, SeekOrigin.Begin)
|
||||
If Header(0) = 82 AndAlso Header(1) = 73 Then
|
||||
'读取 WebP
|
||||
Dim FileBytes(InputStream.Length - 1) As Byte
|
||||
InputStream.Read(FileBytes, 0, FileBytes.Length)
|
||||
Pic = WebPDecoder.DecodeFromBytes(FileBytes) '将代码隔离在另外一个类中,这样只要不走进这个分支就不会加载 Imazen.WebP.dll
|
||||
Else
|
||||
Pic = New System.Drawing.Bitmap(InputStream)
|
||||
End If
|
||||
End Using
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Pic = My.Application.TryFindResource(FilePathOrResourceName)
|
||||
If Pic Is Nothing Then
|
||||
Pic = New System.Drawing.Bitmap(1, 1)
|
||||
Log(ex, $"加载位图失败({FilePathOrResourceName})")
|
||||
Throw
|
||||
Throw New Exception($"加载 MyBitmap 失败({FilePathOrResourceName})", ex)
|
||||
Else
|
||||
Log(ex, $"指定类型有误的位图加载({FilePathOrResourceName})", LogLevel.Developer)
|
||||
Log(ex, $"指定类型有误的 MyBitmap 加载({FilePathOrResourceName})", LogLevel.Developer)
|
||||
Exit Try
|
||||
End If
|
||||
End Try
|
||||
@@ -104,6 +121,13 @@ Public Class MyBitmap
|
||||
Pic = New System.Drawing.Bitmap(MS)
|
||||
End Using
|
||||
End Sub
|
||||
Private Class WebPDecoder '将代码隔离在另外一个类中,这样只要不调用这个方法就不会加载 Imazen.WebP.dll
|
||||
Public Shared Function DecodeFromBytes(Bytes As Byte()) As System.Drawing.Bitmap
|
||||
If Is32BitSystem Then Throw New Exception("不支持在 32 位系统下加载 WebP 图片。")
|
||||
Dim Decoder As New Imazen.WebP.SimpleDecoder()
|
||||
Return Decoder.DecodeFromBytes(Bytes, Bytes.Length)
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 获取裁切的图片,这个方法不会导致原对象改变且会返回一个新的对象。
|
||||
|
||||
Reference in New Issue
Block a user