上传公开代码
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,700 @@
|
||||
Public Module ModLoader
|
||||
|
||||
'各类加载器
|
||||
''' <summary>
|
||||
''' 加载器的统一基类。
|
||||
''' </summary>
|
||||
Public MustInherit Class LoaderBase
|
||||
Implements ILoadingTrigger
|
||||
|
||||
Public ReadOnly Property IsLoader As Boolean = True Implements ILoadingTrigger.IsLoader
|
||||
|
||||
'基础属性
|
||||
''' <summary>
|
||||
''' 加载器的标识编号。
|
||||
''' </summary>
|
||||
Public Uuid As Integer = GetUuid()
|
||||
''' <summary>
|
||||
''' 加载器的名称。
|
||||
''' </summary>
|
||||
Public Name As String = "未命名任务 " & Uuid & "#"
|
||||
''' <summary>
|
||||
''' 用于状态改变检测的同步锁。
|
||||
''' </summary>
|
||||
Public ReadOnly LockState As New Object
|
||||
''' <summary>
|
||||
''' 父加载器。
|
||||
''' </summary>
|
||||
Public Parent As LoaderBase = Nothing
|
||||
''' <summary>
|
||||
''' 最上级的加载器。
|
||||
''' </summary>
|
||||
Public ReadOnly Property RealParent As LoaderBase
|
||||
Get
|
||||
Try
|
||||
RealParent = Parent
|
||||
While RealParent IsNot Nothing AndAlso RealParent.Parent IsNot Nothing
|
||||
RealParent = RealParent.Parent
|
||||
End While
|
||||
Catch ex As Exception
|
||||
Log(ex, "获取父加载器失败(" & Name & ")", LogLevel.Feedback)
|
||||
Return Nothing
|
||||
End Try
|
||||
End Get
|
||||
End Property
|
||||
|
||||
Public Overridable Sub InitParent(Parent As LoaderBase)
|
||||
Me.Parent = Parent
|
||||
End Sub
|
||||
|
||||
'事件
|
||||
|
||||
''' <summary>
|
||||
''' 当状态改变时,在工作线程触发代码。在添加事件后,必须将 HasOnStateChangedThread 设为 True。
|
||||
''' </summary>
|
||||
Public Event OnStateChangedThread(Loader As LoaderBase, NewState As LoadState, OldState As LoadState)
|
||||
Public HasOnStateChangedThread As Boolean = False
|
||||
|
||||
''' <summary>
|
||||
''' 当状态改变时,在 UI 线程触发代码。
|
||||
''' </summary>
|
||||
Public Event OnStateChangedUi(Loader As LoaderBase, NewState As LoadState, OldState As LoadState)
|
||||
''' <summary>
|
||||
''' 简易的在 UI 线程添加触发事件的方式。主要用于在新建 Loader 时直接使用 With 绑定事件,以及进行老代码兼容。
|
||||
''' </summary>
|
||||
Public WriteOnly Property OnStateChanged As Action(Of LoaderBase)
|
||||
Set(value As Action(Of LoaderBase))
|
||||
AddHandler OnStateChangedUi, Sub(Loader As LoaderBase, NewState As LoadState, OldState As LoadState) value(Loader)
|
||||
End Set
|
||||
End Property
|
||||
|
||||
''' <summary>
|
||||
''' 在加载器目标事件执行完成,加载器状态即将变为 Finish 时调用。可以视为扩展加载器目标事件。
|
||||
''' </summary>
|
||||
Public Event PreviewFinish(Loader As LoaderBase)
|
||||
Protected Sub RaisePreviewFinish()
|
||||
RaiseEvent PreviewFinish(Me)
|
||||
End Sub
|
||||
|
||||
'状态监控
|
||||
''' <summary>
|
||||
''' 加载器的状态。
|
||||
''' </summary>
|
||||
Public Property State As LoadState
|
||||
Get
|
||||
Return _State
|
||||
End Get
|
||||
Set(value As LoadState)
|
||||
If _State = value Then Exit Property
|
||||
Dim OldState = _State
|
||||
If value = LoadState.Finished AndAlso Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000))
|
||||
_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)
|
||||
If HasOnStateChangedThread Then RunInThread(Sub() RaiseEvent OnStateChangedThread(Me, value, OldState))
|
||||
End Set
|
||||
End Property
|
||||
Private _State As LoadState = LoadState.Waiting
|
||||
Public Property LoadingState As MyLoading.MyLoadingState Implements ILoadingTrigger.LoadingState
|
||||
Get
|
||||
Return _LoadingState
|
||||
End Get
|
||||
Set(value As MyLoading.MyLoadingState)
|
||||
If _LoadingState = value Then Exit Property
|
||||
Dim OldState = _LoadingState
|
||||
_LoadingState = value
|
||||
RaiseEvent LoadingStateChanged(value, OldState)
|
||||
End Set
|
||||
End Property
|
||||
Private _LoadingState As MyLoading.MyLoadingState = MyLoading.MyLoadingState.Stop
|
||||
Public Event LoadingStateChanged(NewState As MyLoading.MyLoadingState, OldState As MyLoading.MyLoadingState) Implements ILoadingTrigger.LoadingStateChanged
|
||||
''' <summary>
|
||||
''' 若加载器出错,可提供给外部参考的异常。
|
||||
''' </summary>
|
||||
Public Property [Error] As Exception = Nothing
|
||||
''' <summary>
|
||||
''' 使用 LoaderCombo 加载时,该任务是否会阻碍后续任务的进行。
|
||||
''' </summary>
|
||||
Public Block As Boolean = True
|
||||
''' <summary>
|
||||
''' 该加载器是否显示在列表中。
|
||||
''' </summary>
|
||||
Public Show As Boolean = True
|
||||
''' <summary>
|
||||
''' 当前加载器是否由 IsForceRestart 强制调起。
|
||||
''' 这个属性自身不会干任何事,而是提供给加载器执行的函数,使得加载器调用另一个加载器时,可以继承强制重启属性。
|
||||
''' </summary>
|
||||
Public IsForceRestarting As Boolean = False
|
||||
|
||||
'进度监控
|
||||
''' <summary>
|
||||
''' 加载器的执行进度,为 0 至 1 的小数。
|
||||
''' </summary>
|
||||
Public Overridable Property Progress As Double
|
||||
Get
|
||||
Select Case State
|
||||
Case LoadState.Waiting
|
||||
Return 0
|
||||
Case LoadState.Loading
|
||||
Return If(_Progress = -1, 0.02, _Progress)
|
||||
Case Else
|
||||
Return 1
|
||||
End Select
|
||||
End Get
|
||||
Set(value As Double)
|
||||
_Progress = value
|
||||
End Set
|
||||
End Property
|
||||
Private _Progress As Double = -1
|
||||
''' <summary>
|
||||
''' 计算总进度时的权重。它应该为预计时间(秒)。
|
||||
''' </summary>
|
||||
Public Property ProgressWeight As Double = 1
|
||||
|
||||
'状态变化
|
||||
Public MustOverride Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
|
||||
Public MustOverride Sub Abort()
|
||||
|
||||
'等待结束
|
||||
Public Const WaitForExitTimeoutMessage As String = "等待加载器执行超时。"
|
||||
|
||||
''' <summary>
|
||||
''' 无限期地等待加载器完成,直到结束或抛出异常。若加载器尚未开始,则会开始执行。
|
||||
''' </summary>
|
||||
Public Sub WaitForExit(Optional Input As Object = Nothing, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional IsForceRestart As Boolean = False)
|
||||
Start(Input, IsForceRestart)
|
||||
Do While State = LoadState.Loading
|
||||
If LoaderToSyncProgress IsNot Nothing Then LoaderToSyncProgress.Progress = Progress
|
||||
Thread.Sleep(10)
|
||||
Loop
|
||||
If State = LoadState.Finished Then
|
||||
Exit Sub
|
||||
ElseIf State = LoadState.Aborted Then
|
||||
Throw New ThreadInterruptedException("加载器执行已中断。")
|
||||
ElseIf IsNothing([Error]) Then
|
||||
Throw New Exception("未知错误!")
|
||||
Else
|
||||
Throw New Exception([Error].Message, [Error]) '保留调用堆栈,同时不影响信息输出与单元测试
|
||||
End If
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 等待加载器完成,直到结束、抛出异常或超时。若加载器尚未开始,则会开始执行。
|
||||
''' </summary>
|
||||
''' <param name="Timeout">等待的超时时间,以毫秒为单位。</param>
|
||||
''' <param name="TimeoutMessage">若执行超时,将会抛出的异常信息。</param>
|
||||
Public Sub WaitForExitTime(Timeout As Integer, Optional Input As Object = Nothing, Optional TimeoutMessage As String = WaitForExitTimeoutMessage, Optional LoaderToSyncProgress As Object = Nothing, Optional IsForceRestart As Boolean = False)
|
||||
Start(Input, IsForceRestart)
|
||||
Do While State = LoadState.Loading
|
||||
If LoaderToSyncProgress IsNot Nothing Then LoaderToSyncProgress.Progress = Progress
|
||||
Thread.Sleep(10)
|
||||
Timeout -= 10
|
||||
If Timeout < 0 Then Throw New TimeoutException(TimeoutMessage)
|
||||
Loop
|
||||
If State = LoadState.Finished Then
|
||||
Exit Sub
|
||||
ElseIf State = LoadState.Aborted Then
|
||||
Throw New ThreadInterruptedException("加载器执行已中断。")
|
||||
ElseIf IsNothing([Error]) Then
|
||||
Throw New Exception("未知错误!")
|
||||
Else
|
||||
Throw [Error]
|
||||
End If
|
||||
End Sub
|
||||
|
||||
'相同重载
|
||||
Public Overrides Function Equals(obj As Object) As Boolean
|
||||
Dim base = TryCast(obj, LoaderBase)
|
||||
Return base IsNot Nothing AndAlso Uuid = base.Uuid
|
||||
End Function
|
||||
|
||||
End Class
|
||||
''' <summary>
|
||||
''' 用于异步执行并监控单一函数的加载器。
|
||||
''' </summary>
|
||||
Public Class LoaderTask(Of InputType, OutputType)
|
||||
Inherits LoaderBase
|
||||
|
||||
'线程设定
|
||||
Protected Friend ThreadPriority As ThreadPriority
|
||||
'执行事件
|
||||
Protected Friend LoadDelegate As Action(Of LoaderTask(Of InputType, OutputType))
|
||||
Protected Friend InputDelegate As Func(Of Object)
|
||||
'状态指示
|
||||
''' <summary>
|
||||
''' 当前执行线程是否应当中断。只应用在加载器的工作线程中判断,不可跨线程调用。
|
||||
''' </summary>
|
||||
Public ReadOnly Property IsAborted As Boolean
|
||||
Get
|
||||
Return LastRunningThread Is Nothing OrElse Not ReferenceEquals(Thread.CurrentThread, LastRunningThread)
|
||||
End Get
|
||||
End Property
|
||||
''' <summary>
|
||||
''' 在输入相同时使用原有结果的超时,单位为毫秒。
|
||||
''' </summary>
|
||||
Public ReloadTimeout As Integer = -1
|
||||
''' <summary>
|
||||
''' 上次完成加载时的时间。
|
||||
''' </summary>
|
||||
Public LastFinishedTime As Long = 0
|
||||
''' <summary>
|
||||
''' 最后一次运行加载器的线程。可能为 Nothing,或线程已结束。
|
||||
''' </summary>
|
||||
Public LastRunningThread As Thread = Nothing
|
||||
'输入输出
|
||||
Public Input As InputType = Nothing
|
||||
Public Output As OutputType = Nothing
|
||||
|
||||
'获取输入
|
||||
Public Function StartGetInput(Optional Input As InputType = Nothing, Optional InputDelegate As Func(Of Object) = Nothing) As InputType
|
||||
If InputDelegate Is Nothing Then InputDelegate = Me.InputDelegate
|
||||
Dim NewInput As InputType = Nothing '若 InputType 不能为 Nothing,则会导致 Input Is Nothing 永远失败,因此需要额外判断
|
||||
If (Input Is Nothing OrElse (NewInput IsNot Nothing AndAlso Input.Equals(NewInput))) AndAlso InputDelegate IsNot Nothing Then
|
||||
RunInUiWait(Sub() Input = InputDelegate())
|
||||
End If
|
||||
Return Input
|
||||
End Function
|
||||
'代码执行
|
||||
Public Function ShouldStart(ByRef Input As Object, Optional IsForceRestart As Boolean = False, Optional IgnoreReloadTimeout As Boolean = False) As Boolean
|
||||
'获取输入
|
||||
Try
|
||||
Input = StartGetInput(Input)
|
||||
Catch ex As Exception
|
||||
Log(ex, "加载输入获取失败(" & Name & ")", LogLevel.Hint)
|
||||
[Error] = ex
|
||||
SyncLock LockState
|
||||
State = LoadState.Failed
|
||||
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 '正在加载或已加载成功
|
||||
(IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse LastFinishedTime = 0 OrElse GetTimeTick() - LastFinishedTime < ReloadTimeout) Then '没有超时
|
||||
'如果输入相同且未出错则不重试
|
||||
Return False
|
||||
Else
|
||||
'需要开始
|
||||
Return True
|
||||
End If
|
||||
End Function
|
||||
Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
|
||||
'确认是否开始加载
|
||||
If ShouldStart(Input, IsForceRestart) Then
|
||||
'输入不同或失败,开始加载
|
||||
If State = LoadState.Loading Then TriggerThreadAbort()
|
||||
Me.Input = Input
|
||||
SyncLock LockState
|
||||
State = LoadState.Loading
|
||||
End SyncLock
|
||||
Else
|
||||
Exit Sub
|
||||
End If
|
||||
|
||||
LastRunningThread = New Thread(
|
||||
Sub()
|
||||
Try
|
||||
IsForceRestarting = IsForceRestart
|
||||
If ModeDebug Then Log("[Loader] 加载线程 " & Name & " (" & Thread.CurrentThread.ManagedThreadId & ") 已启动")
|
||||
LoadDelegate(Me)
|
||||
If IsAborted Then
|
||||
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 ThreadInterruptedException
|
||||
If ModeDebug Then Log(ex, "加载线程 " & Name & " (" & Thread.CurrentThread.ManagedThreadId & ") 已触发线程中断")
|
||||
'如果线程是因为判断到 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)
|
||||
[Error] = ex
|
||||
State = LoadState.Failed
|
||||
End Try
|
||||
End Sub) With {.Name = Name, .Priority = ThreadPriority}
|
||||
LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsAborted
|
||||
End Sub
|
||||
Public Overrides Sub Abort()
|
||||
If State <> LoadState.Loading Then Exit Sub
|
||||
SyncLock LockState
|
||||
State = LoadState.Aborted
|
||||
End SyncLock
|
||||
TriggerThreadAbort()
|
||||
End Sub
|
||||
Private Sub TriggerThreadAbort()
|
||||
If LastRunningThread Is Nothing Then Exit Sub
|
||||
If ModeDebug Then Log("[Loader] 加载线程 " & Name & " (" & LastRunningThread.ManagedThreadId & ") 已中断")
|
||||
If LastRunningThread.IsAlive Then LastRunningThread.Interrupt()
|
||||
LastRunningThread = Nothing
|
||||
End Sub
|
||||
|
||||
Public Sub New()
|
||||
'仅仅是为了避免一些智障报错(继承类必须重写 New 的情况)
|
||||
End Sub
|
||||
Public Sub New(Name As String, LoadDelegate As Action(Of LoaderTask(Of InputType, OutputType)), Optional InputDelegate As Func(Of InputType) = Nothing, Optional Priority As ThreadPriority = ThreadPriority.Normal)
|
||||
Me.Name = Name
|
||||
Me.LoadDelegate = LoadDelegate
|
||||
Me.InputDelegate = InputDelegate
|
||||
ThreadPriority = Priority
|
||||
End Sub
|
||||
|
||||
End Class
|
||||
''' <summary>
|
||||
''' 支持多个加载器连续运作的复合加载器。
|
||||
''' </summary>
|
||||
Public Class LoaderCombo(Of InputType)
|
||||
Inherits LoaderBase
|
||||
|
||||
Public Overrides Property Progress() As Double
|
||||
Get
|
||||
Select Case State
|
||||
Case LoadState.Waiting
|
||||
Return 0
|
||||
Case LoadState.Loading
|
||||
Dim Total As Double = 0, Finished As Double = 0
|
||||
For Each Loader In Loaders
|
||||
Total += Loader.ProgressWeight
|
||||
Finished += Loader.ProgressWeight * Loader.Progress
|
||||
Next
|
||||
If Total = 0 Then Return 0
|
||||
Return Finished / Total
|
||||
Case Else
|
||||
Return 1
|
||||
End Select
|
||||
End Get
|
||||
Set(value As Double)
|
||||
Throw New Exception("多重加载器不支持设置进度")
|
||||
End Set
|
||||
End Property
|
||||
|
||||
Public Loaders As New List(Of LoaderBase)
|
||||
Public Input As InputType
|
||||
Public Sub New(Name As String, Loaders As IEnumerable(Of LoaderBase))
|
||||
Me.Loaders.Clear()
|
||||
For Each Loader As LoaderBase In Loaders
|
||||
If Loader IsNot Nothing Then
|
||||
Me.Loaders.Add(Loader)
|
||||
AddHandler Loader.OnStateChangedThread, AddressOf SubTaskStateChanged
|
||||
Loader.HasOnStateChangedThread = True
|
||||
End If
|
||||
Next
|
||||
InitParent(Nothing)
|
||||
Me.Name = Name
|
||||
End Sub
|
||||
Public Overrides Sub InitParent(Parent As LoaderBase)
|
||||
Me.Parent = Parent
|
||||
For Each Loader In Loaders
|
||||
Loader.InitParent(Me)
|
||||
Next
|
||||
End Sub
|
||||
Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
|
||||
IsForceRestarting = IsForceRestart
|
||||
'改变状态
|
||||
SyncLock LockState
|
||||
If State = LoadState.Loading Then
|
||||
Exit Sub
|
||||
Else
|
||||
State = LoadState.Loading
|
||||
End If
|
||||
End SyncLock
|
||||
'启动加载
|
||||
Me.Input = Input
|
||||
If IsForceRestart Then
|
||||
For Each Loader In Loaders
|
||||
Loader.State = LoadState.Waiting
|
||||
Next
|
||||
End If
|
||||
Update()
|
||||
End Sub
|
||||
Public Overrides Sub Abort()
|
||||
'改变状态
|
||||
SyncLock LockState
|
||||
If State = LoadState.Loading OrElse State = LoadState.Waiting Then
|
||||
State = LoadState.Aborted
|
||||
Else
|
||||
Exit Sub
|
||||
End If
|
||||
End SyncLock
|
||||
RunInThread(Sub()
|
||||
'中断加载器
|
||||
For Each Loader In Loaders
|
||||
Loader.Abort()
|
||||
Next
|
||||
'从列表删除
|
||||
SyncLock LoaderTaskbarLock
|
||||
If LoaderTaskbar.Contains(Me) Then LoaderTaskbar.Remove(Me)
|
||||
End SyncLock
|
||||
End Sub)
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' 子任务状态变更。
|
||||
''' </summary>
|
||||
Private Sub SubTaskStateChanged(Loader As LoaderBase, NewState As LoadState, OldState As LoadState)
|
||||
Select Case NewState
|
||||
Case LoadState.Loading
|
||||
'开始,啥都不干
|
||||
Case LoadState.Waiting
|
||||
'子加载器可能由于外部输入改变而暂时变为 Waiting,之后会立即重新启动
|
||||
'所以啥都不干就行
|
||||
Case LoadState.Finished
|
||||
'正常结束,触发刷新
|
||||
Update()
|
||||
Case LoadState.Aborted
|
||||
'被中断,这个任务也中断
|
||||
Abort()
|
||||
Case Else
|
||||
'完蛋,出错了
|
||||
SyncLock LockState
|
||||
If State >= LoadState.Finished Then Exit Sub
|
||||
[Error] = New Exception(Loader.Name & "失败", Loader.Error)
|
||||
State = Loader.State
|
||||
End SyncLock
|
||||
For Each Loader In Loaders
|
||||
Loader.Abort()
|
||||
Next
|
||||
FrmMain.BtnExtraDownload.ShowRefresh()
|
||||
Exit Sub
|
||||
End Select
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 触发一次更新,以启动新加载器或完成。
|
||||
''' </summary>
|
||||
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 Input As Object = Me.Input
|
||||
For Each Loader In Loaders
|
||||
Select Case Loader.State
|
||||
Case LoadState.Finished
|
||||
'检查是否需要重启
|
||||
If Loader.GetType.Name.StartsWith("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)
|
||||
GoTo Restart
|
||||
End If
|
||||
'更新下一个加载器的输入
|
||||
Input = CType(Loader, Object).Output
|
||||
End If
|
||||
Case LoadState.Loading
|
||||
'检查是否需要重启
|
||||
If Loader.GetType.Name.StartsWith("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)
|
||||
GoTo Restart
|
||||
End If
|
||||
End If
|
||||
'已经有正在加载中的了,不需要再启动了
|
||||
IsFinished = False
|
||||
Ignore = True
|
||||
Case Else
|
||||
Restart:
|
||||
'未启动,则启动加载器
|
||||
IsFinished = False
|
||||
If Ignore Then Continue For
|
||||
If Input IsNot Nothing Then
|
||||
'若输入类型与下一个加载器相同才继续
|
||||
Dim LoaderType As String = Loader.GetType.Name
|
||||
If LoaderType.StartsWith("LoaderTask") OrElse LoaderType.StartsWith("LoaderCombo") Then
|
||||
Loader.Start(If(Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IsForceRestarting)
|
||||
ElseIf LoaderType.StartsWith("LoaderDownload") Then
|
||||
Loader.Start(If(TypeOf Input Is List(Of NetFile), Input, Nothing), IsForceRestarting)
|
||||
Else
|
||||
Throw New Exception("未知的加载器类型(" & LoaderType & ")")
|
||||
End If
|
||||
Else
|
||||
Loader.Start(IsForceRestart:=IsForceRestarting)
|
||||
End If
|
||||
'如果不让继续启动,那就不启动呗
|
||||
If Loader.Block Then Ignore = True
|
||||
End Select
|
||||
Next
|
||||
If IsFinished Then
|
||||
'顺利完成,贼棒
|
||||
RaisePreviewFinish()
|
||||
State = LoadState.Finished
|
||||
FrmMain.BtnExtraDownload.ShowRefresh()
|
||||
End If
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' 获得最底层的,应被显示给用户的加载器列表,并追加于 List。
|
||||
''' </summary>
|
||||
Public Shared Sub GetLoaderList(Loader As Object, ByRef List As List(Of LoaderBase), Optional RequireShow As Boolean = True)
|
||||
For Each SubLoader In Loader.Loaders
|
||||
If SubLoader.Show OrElse Not RequireShow Then List.Add(SubLoader)
|
||||
If SubLoader.GetType.Name.StartsWith("LoaderCombo") Then GetLoaderList(SubLoader, List)
|
||||
Next
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 获得最底层的,应被显示给用户的加载器列表,并追加于 List。
|
||||
''' </summary>
|
||||
Public Sub GetLoaderList(ByRef List As List(Of LoaderBase), Optional RequireShow As Boolean = True)
|
||||
GetLoaderList(Me, List, RequireShow)
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 获得最底层的,应被显示给用户的加载器列表。
|
||||
''' </summary>
|
||||
Public Function GetLoaderList(Optional RequireShow As Boolean = True) As List(Of LoaderBase)
|
||||
Dim List As New List(Of LoaderBase)
|
||||
GetLoaderList(List, RequireShow)
|
||||
Return List
|
||||
End Function
|
||||
|
||||
End Class
|
||||
|
||||
'任务栏进度条
|
||||
Public LoaderTaskbarLock As New Object
|
||||
Public LoaderTaskbar As New List(Of LoaderBase)
|
||||
Public LoaderTaskbarProgress As Double = 0 '平滑后的进度
|
||||
Private LoaderTaskbarProgressLast As Shell.TaskbarItemProgressState = Shell.TaskbarItemProgressState.None
|
||||
|
||||
Public Sub LoaderTaskbarAdd(Loader As LoaderBase)
|
||||
If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRemove(Loader)
|
||||
SyncLock LoaderTaskbarLock
|
||||
LoaderTaskbar.Add(Loader)
|
||||
End SyncLock
|
||||
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.Show AndAlso Loader.State = LoadState.Loading Then IsAllDownloadTaskCompleted = False
|
||||
Next
|
||||
'若单个任务已中止或全部任务已完成,则刷新并移除
|
||||
Dim RemovedTask As New List(Of LoaderBase)
|
||||
For i = 0 To LoaderTaskbar.Count - 1
|
||||
If (IsAllDownloadTaskCompleted OrElse LoaderTaskbar(i).State = LoadState.Aborted) AndAlso LoaderTaskbar(i).Show Then
|
||||
If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRefresh(LoaderTaskbar(i))
|
||||
RemovedTask.Add(LoaderTaskbar(i))
|
||||
End If
|
||||
Next
|
||||
For Each Task In RemovedTask
|
||||
LoaderTaskbar.Remove(Task)
|
||||
Next
|
||||
End SyncLock
|
||||
'更新平滑后的进度
|
||||
If NewProgress <= 0 OrElse NewProgress >= 1 OrElse LoaderTaskbarProgress > NewProgress Then
|
||||
LoaderTaskbarProgress = NewProgress
|
||||
Else
|
||||
LoaderTaskbarProgress = LoaderTaskbarProgress * 0.9 + NewProgress * 0.1
|
||||
End If
|
||||
'更新任务栏信息
|
||||
If LoaderTaskbar.Count = 0 OrElse LoaderTaskbarProgress = 1 Then
|
||||
NewState = Shell.TaskbarItemProgressState.None
|
||||
ElseIf LoaderTaskbarProgress < 0.015 Then
|
||||
NewState = Shell.TaskbarItemProgressState.Indeterminate
|
||||
Else
|
||||
NewState = Shell.TaskbarItemProgressState.Normal
|
||||
FrmMain.TaskbarItemInfo.ProgressValue = LoaderTaskbarProgress
|
||||
End If
|
||||
If LoaderTaskbarProgressLast <> NewState Then
|
||||
LoaderTaskbarProgressLast = NewState
|
||||
FrmMain.TaskbarItemInfo.ProgressState = NewState
|
||||
FrmMain.BtnExtraDownload.ShowRefresh()
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Log(ex, "刷新任务栏进度显示失败", LogLevel.Feedback)
|
||||
End Try
|
||||
End Sub
|
||||
Public Function LoaderTaskbarProgressGet() As Double
|
||||
Try
|
||||
Dim Total As Double = 0, Count As Integer = 0
|
||||
SyncLock LoaderTaskbarLock
|
||||
For i = 0 To LoaderTaskbar.Count - 1
|
||||
Total += LoaderTaskbar(i).Progress
|
||||
Count += 1 '避免多线程影响导致计数出错
|
||||
Next
|
||||
End SyncLock
|
||||
If Count = 0 Then Return 1
|
||||
Return MathRange(Total / Count, 0, 1)
|
||||
Catch ex As Exception
|
||||
Log(ex, "获取任务栏进度出错", LogLevel.Feedback)
|
||||
Return 0.5
|
||||
End Try
|
||||
End Function
|
||||
|
||||
'文件夹刷新类委托
|
||||
Private LoaderFolderDictionary As New Dictionary(Of LoaderBase, LoaderFolderDictionaryEntry)
|
||||
Private Structure LoaderFolderDictionaryEntry
|
||||
Public LastCheckTime As Date?
|
||||
Public FolderPath As String
|
||||
Public Overrides Function Equals(obj As Object) As Boolean
|
||||
If TypeOf obj IsNot LoaderFolderDictionaryEntry Then Return False
|
||||
Dim entry = DirectCast(obj, LoaderFolderDictionaryEntry)
|
||||
Return EqualityComparer(Of Date?).Default.Equals(LastCheckTime, entry.LastCheckTime) AndAlso
|
||||
FolderPath = entry.FolderPath
|
||||
End Function
|
||||
End Structure
|
||||
Public Enum LoaderFolderRunType
|
||||
RunOnUpdated
|
||||
ForceRun
|
||||
UpdateOnly
|
||||
End Enum
|
||||
''' <summary>
|
||||
''' 执行以文件夹检测作为输入的加载器。加载器需以文件夹路径为输入值。
|
||||
''' 返回是否执行了加载器。
|
||||
''' </summary>
|
||||
''' <param name="ExtraPath">用于检查文件夹修改的额外路径。该路径不会传入加载器。</param>
|
||||
Public Function LoaderFolderRun(Loader As LoaderBase, FolderPath As String, Type As LoaderFolderRunType, Optional MaxDepth As Integer = 0, Optional ExtraPath As String = "", Optional WaitForExit As Boolean = False) As Boolean
|
||||
Dim FolderInfo As DirectoryInfo
|
||||
Dim Value As New LoaderFolderDictionaryEntry With {.FolderPath = FolderPath & ExtraPath, .LastCheckTime = Nothing}
|
||||
Try
|
||||
'获取数据
|
||||
FolderInfo = New DirectoryInfo(FolderPath & ExtraPath)
|
||||
Value.LastCheckTime = If(FolderInfo.Exists, GetActualLastWriteTimeUtc(FolderInfo, MaxDepth), DirectCast(Nothing, Date?))
|
||||
'如果已经检查过,则跳过
|
||||
If Type = LoaderFolderRunType.RunOnUpdated AndAlso LoaderFolderDictionary.ContainsKey(Loader) Then
|
||||
If FolderInfo.Exists Then
|
||||
If LoaderFolderDictionary(Loader).LastCheckTime IsNot Nothing AndAlso
|
||||
Value.Equals(LoaderFolderDictionary(Loader)) Then Return False
|
||||
Else
|
||||
If LoaderFolderDictionary(Loader).LastCheckTime Is Nothing Then Return False
|
||||
End If
|
||||
End If
|
||||
Catch ex As Exception
|
||||
Log(ex, "文件夹加载器启动检测出错")
|
||||
End Try
|
||||
'写入检查数据
|
||||
DictionaryAdd(LoaderFolderDictionary, Loader, Value)
|
||||
'开始检查
|
||||
If Type = LoaderFolderRunType.UpdateOnly Then Return False
|
||||
If WaitForExit Then
|
||||
Loader.WaitForExit(FolderPath, IsForceRestart:=True)
|
||||
Else
|
||||
Loader.Start(FolderPath, IsForceRestart:=True)
|
||||
End If
|
||||
Return True
|
||||
End Function
|
||||
Private Function GetActualLastWriteTimeUtc(FolderInfo As DirectoryInfo, MaxDepth As Integer) As Date
|
||||
Dim Time As Date = FolderInfo.LastWriteTimeUtc
|
||||
If MaxDepth > 0 Then
|
||||
For Each Folder In FolderInfo.EnumerateDirectories
|
||||
Dim FolderTime As Date = GetActualLastWriteTimeUtc(Folder, MaxDepth - 1)
|
||||
If FolderTime > Time Then Time = FolderTime
|
||||
Next
|
||||
End If
|
||||
Return Time
|
||||
End Function
|
||||
|
||||
End Module
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,315 @@
|
||||
'提供不同的输入验证方法,名称以 Validate 开头
|
||||
|
||||
''' <summary>
|
||||
''' 输入验证规则基类。要查看所有的输入验证规则,可在输入 Validate 后查看自动补全。
|
||||
''' </summary>
|
||||
Public MustInherit Class Validate
|
||||
''' <summary>
|
||||
''' 验证某字符串是否符合验证要求。若符合,返回空字符串;若不符合,返回错误原因。
|
||||
''' </summary>
|
||||
Public MustOverride Function Validate(Str As String) As String
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 若为空则直接通过检查。
|
||||
''' </summary>
|
||||
Public Class ValidateNullable
|
||||
Inherits Validate
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
If IsNothing(Str) OrElse String.IsNullOrEmpty(Str) Then Return Nothing
|
||||
Return ""
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 不能为 Nothing 或空字符串(不包括全空格检查)。
|
||||
''' </summary>
|
||||
Public Class ValidateNullOrEmpty
|
||||
Inherits Validate
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
If IsNothing(Str) OrElse String.IsNullOrEmpty(Str) Then Return "输入内容不能为空!"
|
||||
Return ""
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 不能为 Nothing 或空字符串(包括全空格检查)。
|
||||
''' </summary>
|
||||
Public Class ValidateNullOrWhiteSpace
|
||||
Inherits Validate
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
If IsNothing(Str) OrElse String.IsNullOrWhiteSpace(Str) Then Return "输入内容不能为空!"
|
||||
Return ""
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 必须满足正则表达式。
|
||||
''' </summary>
|
||||
Public Class ValidateRegex
|
||||
Inherits Validate
|
||||
Public Property Regex As String
|
||||
Public Property ErrorDescription As String = ""
|
||||
Public Sub New()
|
||||
End Sub '用于 XAML 初始化
|
||||
Public Sub New(Regex As String, Optional ErrorDescription As String = "")
|
||||
Me.Regex = Regex
|
||||
Me.ErrorDescription = ErrorDescription
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
If Not RegexCheck(Str, Regex) Then Return ErrorDescription
|
||||
Return ""
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 必须是一个完整网址。
|
||||
''' </summary>
|
||||
Public Class ValidateHttp
|
||||
Inherits Validate
|
||||
Public Sub New()
|
||||
End Sub '用于 XAML 初始化
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
If Str.EndsWith("/") Then Str = Str.Substring(0, Str.Length - 1)
|
||||
If Not RegexCheck(Str, "^(http[s]?)\://") Then Return "输入的网址无效!"
|
||||
Return ""
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 必须为整数。
|
||||
''' </summary>
|
||||
Public Class ValidateInteger
|
||||
Inherits Validate
|
||||
Public Property Min As Integer
|
||||
Public Property Max As Integer = Integer.MaxValue
|
||||
Public Sub New()
|
||||
End Sub '用于 XAML 初始化
|
||||
Public Sub New(Min As Integer, Max As Integer)
|
||||
Me.Min = Min
|
||||
Me.Max = Max
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
If Str.Length > 9 Then Return "请输入一个大小合理的数字!"
|
||||
Dim Valed As Integer = Val(Str)
|
||||
If Valed.ToString <> Str Then Return "请输入一个整数!"
|
||||
If Val(Str) > Max Then Return "不可超过 " & Max & "!"
|
||||
If Val(Str) < Min Then Return "不可低于 " & Min & "!"
|
||||
Return ""
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 长度限制。
|
||||
''' </summary>
|
||||
Public Class ValidateLength
|
||||
Inherits Validate
|
||||
Public Property Min As Integer = 0
|
||||
Public Property Max As Integer = Integer.MaxValue
|
||||
Public Sub New()
|
||||
End Sub '用于 XAML 初始化
|
||||
Public Sub New(Min As Integer, Optional Max As Integer = Integer.MaxValue)
|
||||
Me.Min = Min
|
||||
Me.Max = Max
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
If Len(Str) <> Max AndAlso Max = Min Then Return "长度必须为 " & Max & " 个字符!"
|
||||
If Len(Str) > Max Then Return "长度最长为 " & Max & " 个字符!"
|
||||
If Len(Str) < Min Then Return "长度至少需 " & Min & " 个字符!"
|
||||
Return ""
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 不能包含某些特定字符串。忽略大小写。
|
||||
''' </summary>
|
||||
Public Class ValidateExcept
|
||||
Inherits Validate
|
||||
Public Property Excepts As ObjectModel.Collection(Of String) = New ObjectModel.Collection(Of String)
|
||||
Public Property ErrorMessage As String
|
||||
Public Sub New()
|
||||
ErrorMessage = "输入内容不能包含 %!"
|
||||
End Sub '用于 XAML 初始化
|
||||
Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能包含 %!")
|
||||
Me.Excepts = Excepts
|
||||
Me.ErrorMessage = ErrorMessage
|
||||
End Sub
|
||||
Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能包含 %!")
|
||||
Me.Excepts = New ObjectModel.Collection(Of String)
|
||||
Me.ErrorMessage = ErrorMessage
|
||||
For Each Data As String In Excepts
|
||||
Me.Excepts.Add(Data)
|
||||
Next
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
For Each Ch As String In Excepts
|
||||
If Str.IndexOf(Ch, StringComparison.OrdinalIgnoreCase) >= 0 Then
|
||||
If IsNothing(ErrorMessage) Then ErrorMessage = ""
|
||||
Return ErrorMessage.Replace("%", Ch)
|
||||
End If
|
||||
Next
|
||||
Return ""
|
||||
End Function
|
||||
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 不能与某些特定字符串相同。
|
||||
''' </summary>
|
||||
Public Class ValidateExceptSame
|
||||
Inherits Validate
|
||||
Public Property Excepts As ObjectModel.Collection(Of String) = New ObjectModel.Collection(Of String)
|
||||
Public Property ErrorMessage As String
|
||||
Public Property IgnoreCase As Boolean = False
|
||||
Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能为 %!", Optional IgnoreCase As Boolean = False)
|
||||
Me.Excepts = Excepts
|
||||
Me.ErrorMessage = ErrorMessage
|
||||
Me.IgnoreCase = IgnoreCase
|
||||
End Sub
|
||||
Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能为 %!", Optional IgnoreCase As Boolean = False)
|
||||
Me.Excepts = New ObjectModel.Collection(Of String)
|
||||
For Each Data As String In Excepts
|
||||
Me.Excepts.Add(Data)
|
||||
Next
|
||||
Me.ErrorMessage = ErrorMessage
|
||||
Me.IgnoreCase = IgnoreCase
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
If Str Is Nothing Then Return ErrorMessage.Replace("%", "null")
|
||||
For Each Ch As String In Excepts
|
||||
If IgnoreCase Then
|
||||
If Str.ToLower = Ch.ToLower Then Return ErrorMessage.Replace("%", Ch)
|
||||
Else
|
||||
If Str.Equals(Ch) Then Return ErrorMessage.Replace("%", Ch) '使用 = 不确定是否会忽略大小写
|
||||
End If
|
||||
Next
|
||||
Return ""
|
||||
End Function
|
||||
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 对文件夹名的粗略的特化检测。
|
||||
''' </summary>
|
||||
Public Class ValidateFolderName
|
||||
Inherits Validate
|
||||
Public Property Path As String
|
||||
Public Property UseMinecraftCharCheck As Boolean = True
|
||||
Public Property IgnoreCase As Boolean = True
|
||||
Private ReadOnly PathIgnore As IEnumerable(Of DirectoryInfo)
|
||||
Public Sub New(Path As String, Optional UseMinecraftCharCheck As Boolean = True, Optional IgnoreCase As Boolean = True)
|
||||
Me.Path = Path
|
||||
Me.IgnoreCase = IgnoreCase
|
||||
Me.UseMinecraftCharCheck = UseMinecraftCharCheck
|
||||
On Error Resume Next
|
||||
PathIgnore = New DirectoryInfo(Path).EnumerateDirectories
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
Try
|
||||
'检查是否为空
|
||||
Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
|
||||
If Not LengthCheck = "" Then Return LengthCheck
|
||||
'检查空格
|
||||
If Str.StartsWith(" ") Then Return "文件夹名不能以空格开头!"
|
||||
If Str.EndsWith(" ") Then Return "文件夹名不能以空格结尾!"
|
||||
'检查长度
|
||||
LengthCheck = New ValidateLength(1, 100).Validate(Str)
|
||||
If Not LengthCheck = "" Then Return LengthCheck
|
||||
'检查尾部小数点
|
||||
If Str.EndsWith(".") Then Return "文件夹名不能以小数点结尾!"
|
||||
'检查特殊字符
|
||||
Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件夹名不可包含 % 字符!").Validate(Str)
|
||||
If Not CharactCheck = "" Then Return CharactCheck
|
||||
'检查特殊字符串
|
||||
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(Str)
|
||||
If Not InvalidStrCheck = "" Then Return InvalidStrCheck
|
||||
'检查文件夹重名
|
||||
Dim Arr As New List(Of String)
|
||||
If PathIgnore IsNot Nothing Then
|
||||
For Each Folder As DirectoryInfo In PathIgnore
|
||||
Arr.Add(Folder.Name)
|
||||
Next
|
||||
End If
|
||||
Dim SameNameCheck = New ValidateExceptSame(Arr, "不可与现有文件夹重名!", IgnoreCase).Validate(Str)
|
||||
If Not SameNameCheck = "" Then Return SameNameCheck
|
||||
Return ""
|
||||
Catch ex As Exception
|
||||
Log(ex, "检查文件夹名出错")
|
||||
Return "错误:" & ex.Message
|
||||
End Try
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 要求输入一个可用的文件夹路径。
|
||||
''' </summary>
|
||||
Public Class ValidateFolderPath
|
||||
Inherits Validate
|
||||
Public Property UseMinecraftCharCheck As Boolean = True
|
||||
Public Sub New()
|
||||
End Sub
|
||||
Public Sub New(UseMinecraftCharCheck As Boolean)
|
||||
Me.UseMinecraftCharCheck = UseMinecraftCharCheck
|
||||
End Sub
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
'去除尾部斜线,统一为 \
|
||||
Str = Str.Replace("/", "\")
|
||||
If Not Str.TrimEnd("\").EndsWith(":") Then Str = Str.TrimEnd("\")
|
||||
'检查是否为空
|
||||
Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
|
||||
If Not LengthCheck = "" Then Return LengthCheck
|
||||
'检查长度
|
||||
LengthCheck = New ValidateLength(1, 254).Validate(Str)
|
||||
If Not LengthCheck = "" Then Return LengthCheck
|
||||
'检查开头
|
||||
If Str.StartsWith("\\Mac\") Then GoTo Fin
|
||||
For Each Drive As DriveInfo In My.Computer.FileSystem.Drives
|
||||
If Str.ToUpper = Drive.Name Then Return ""
|
||||
If Str.ToUpper.StartsWith(Drive.Name) Then GoTo Fin
|
||||
Next
|
||||
Return "文件夹路径头存在错误!"
|
||||
Fin:
|
||||
'对首层以外的路径检查
|
||||
For i = If(Str.StartsWith("\\Mac\"), 2, 1) To Str.Split("\").Count - 1
|
||||
Dim SubStr As String = Str.Split("\")(i)
|
||||
'检查是否为空
|
||||
Dim SubLengthCheck As String = New ValidateNullOrWhiteSpace().Validate(SubStr)
|
||||
If Not SubLengthCheck = "" Then Return "文件夹路径存在错误!"
|
||||
'检查特殊字符
|
||||
Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "路径中存在无效字符!").Validate(SubStr)
|
||||
If Not CharactCheck = "" Then Return CharactCheck
|
||||
'检查头部空格
|
||||
If SubStr.StartsWith(" ") Then Return "文件夹名不能以空格开头!"
|
||||
If SubStr.EndsWith(" ") Then Return "文件夹名不能以空格结尾!"
|
||||
'检查尾部小数点
|
||||
If SubStr.EndsWith(".") Then Return "文件夹名不能以小数点结尾!"
|
||||
'检查特殊字符串
|
||||
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
|
||||
Next
|
||||
Return ""
|
||||
End Function
|
||||
End Class
|
||||
|
||||
''' <summary>
|
||||
''' 敏感词过滤。
|
||||
''' </summary>
|
||||
Public Class ValidateFilter
|
||||
Inherits Validate
|
||||
Private Shared Excepts As String() = SecretDecrypt(SecretSensitiveWordsRaw).Split("|")
|
||||
Public Sub New()
|
||||
End Sub '用于 XAML 初始化
|
||||
Public Overrides Function Validate(Str As String) As String
|
||||
'提取无间隔符的字符串
|
||||
Dim RealStr As String = ""
|
||||
For Each St In If(Str, "").ToLower
|
||||
If String.IsNullOrWhiteSpace(St.ToString) OrElse ("!@#$%^&*()_+-={}[]|\;':"",./<>? ~ ·!¥…()—,。、《》?‘;:【】{}" & vbLQ & vbRQ).ToCharArray.Contains(St) Then Continue For
|
||||
RealStr += St
|
||||
Next
|
||||
'检查
|
||||
For Each Except In Excepts
|
||||
If RealStr.Contains(Except) Then Return "存在敏感内容!"
|
||||
Next
|
||||
Return ""
|
||||
End Function
|
||||
End Class
|
||||
@@ -0,0 +1,158 @@
|
||||
'一个万能的自动图片类型转换工具类
|
||||
|
||||
Public Class MyBitmap
|
||||
|
||||
''' <summary>
|
||||
''' 位图缓存。
|
||||
''' </summary>
|
||||
Public Shared BitmapCache As New Dictionary(Of String, MyBitmap)
|
||||
|
||||
''' <summary>
|
||||
''' 存储的图片
|
||||
''' </summary>
|
||||
Public Pic As System.Drawing.Bitmap
|
||||
|
||||
'自动类型转换
|
||||
'支持的类:Image,ImageSource,Bitmap,ImageBrush,BitmapSource
|
||||
Public Shared Widening Operator CType(Image As System.Drawing.Image) As MyBitmap
|
||||
Return New MyBitmap(Image)
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As MyBitmap) As System.Drawing.Image
|
||||
Return Image.Pic
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As ImageSource) As MyBitmap
|
||||
Return New MyBitmap(Image)
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As MyBitmap) As ImageSource
|
||||
Dim Bitmap = Image.Pic
|
||||
Dim rect = New System.Drawing.Rectangle(0, 0, Bitmap.Width, Bitmap.Height)
|
||||
Dim bitmapData = Bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format32bppArgb)
|
||||
Try
|
||||
Dim size = rect.Width * rect.Height * 4
|
||||
Return BitmapSource.Create(Bitmap.Width, Bitmap.Height, DPI, DPI, PixelFormats.Bgra32, Nothing, bitmapData.Scan0, size, bitmapData.Stride)
|
||||
Finally
|
||||
Bitmap.UnlockBits(bitmapData)
|
||||
End Try
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As System.Drawing.Bitmap) As MyBitmap
|
||||
Return New MyBitmap(Image)
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As MyBitmap) As System.Drawing.Bitmap
|
||||
Return Image.Pic
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As ImageBrush) As MyBitmap
|
||||
Return New MyBitmap(Image)
|
||||
End Operator
|
||||
Public Shared Widening Operator CType(Image As MyBitmap) As ImageBrush
|
||||
Return New ImageBrush(New MyBitmap(Image.Pic))
|
||||
End Operator
|
||||
|
||||
'构造函数
|
||||
Public Sub New()
|
||||
End Sub
|
||||
Public Sub New(FilePathOrResourceName As String)
|
||||
Try
|
||||
If FilePathOrResourceName.StartsWith(PathImage) Then
|
||||
'使用缓存
|
||||
If BitmapCache.ContainsKey(FilePathOrResourceName) Then
|
||||
Pic = BitmapCache(FilePathOrResourceName).Pic
|
||||
Else
|
||||
Pic = New MyBitmap(CType((New ImageSourceConverter).ConvertFromString(FilePathOrResourceName), ImageSource))
|
||||
BitmapCache.Add(FilePathOrResourceName, Pic)
|
||||
End If
|
||||
Else
|
||||
'使用这种自己接管 FileStream 的方法加载才能解除文件占用
|
||||
Using InputStream As New FileStream(FilePathOrResourceName, FileMode.Open)
|
||||
Pic = New System.Drawing.Bitmap(InputStream)
|
||||
InputStream.Dispose()
|
||||
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
|
||||
Else
|
||||
Log(ex, "指定类型有误的位图加载(" & FilePathOrResourceName & ")", LogLevel.Developer)
|
||||
Exit Try
|
||||
End If
|
||||
End Try
|
||||
End Sub
|
||||
Public Sub New(Image As ImageSource)
|
||||
Using MS = New MemoryStream()
|
||||
Dim Encoder = New PngBitmapEncoder()
|
||||
Encoder.Frames.Add(BitmapFrame.Create(Image))
|
||||
Encoder.Save(MS)
|
||||
Pic = New System.Drawing.Bitmap(MS)
|
||||
End Using
|
||||
End Sub
|
||||
Public Sub New(Image As System.Drawing.Image)
|
||||
Pic = Image
|
||||
End Sub
|
||||
Public Sub New(Image As System.Drawing.Bitmap)
|
||||
Pic = Image
|
||||
End Sub
|
||||
Public Sub New(Image As ImageBrush)
|
||||
Using MS = New MemoryStream()
|
||||
Dim Encoder = New BmpBitmapEncoder()
|
||||
Encoder.Frames.Add(BitmapFrame.Create(Image.ImageSource))
|
||||
Encoder.Save(MS)
|
||||
Pic = New System.Drawing.Bitmap(MS)
|
||||
End Using
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' 获取旋转的图片,这个方法不会导致原对象改变且会返回一个新的对象。图片长宽必须相等。
|
||||
''' </summary>
|
||||
''' <param name="angle">旋转角度(单位为角度)。</param>
|
||||
Public Function Rotation(angle As Double) As System.Drawing.Bitmap
|
||||
With Me
|
||||
Dim img As System.Drawing.Image = Me.Pic
|
||||
Dim bitSize As Single = img.Width
|
||||
Dim bmp As New System.Drawing.Bitmap(CInt(bitSize), CInt(bitSize))
|
||||
Using g As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(bmp)
|
||||
g.TranslateTransform(bitSize / 2, bitSize / 2)
|
||||
g.RotateTransform(angle)
|
||||
g.TranslateTransform(-bitSize / 2, -bitSize / 2)
|
||||
g.DrawImage(img, New System.Drawing.Rectangle(0, 0, img.Width, img.Width))
|
||||
End Using
|
||||
Return bmp
|
||||
End With
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 获取裁切的图片,这个方法不会导致原对象改变且会返回一个新的对象。
|
||||
''' </summary>
|
||||
Public Function Clip(rect As System.Drawing.Rectangle) As System.Drawing.Bitmap
|
||||
With Me
|
||||
Dim img As System.Drawing.Image = Pic
|
||||
Dim bmp As New System.Drawing.Bitmap(rect.Width, rect.Height)
|
||||
Using g As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(bmp)
|
||||
g.DrawImageUnscaled(img, rect)
|
||||
End Using
|
||||
Return bmp
|
||||
End With
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 获取左右翻转的图片,这个方法不会导致原对象改变。
|
||||
''' </summary>
|
||||
Public Function LeftRightFilp() As System.Drawing.Bitmap
|
||||
Dim bmp As New System.Drawing.Bitmap(Pic)
|
||||
bmp.RotateFlip(System.Drawing.RotateFlipType.RotateNoneFlipX)
|
||||
Return bmp
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
''' 将图像保存到文件。
|
||||
''' </summary>
|
||||
Public Sub Save(FilePath As String)
|
||||
Dim encoder As BitmapEncoder = New PngBitmapEncoder()
|
||||
encoder.Frames.Add(BitmapFrame.Create(Me))
|
||||
Using fileStream = New System.IO.FileStream(FilePath, FileMode.Create)
|
||||
encoder.Save(fileStream)
|
||||
End Using
|
||||
End Sub
|
||||
|
||||
End Class
|
||||
Reference in New Issue
Block a user