| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791 |
- 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
- Logger.Error(ex, $"获取父加载器失败({Name})")
- 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 Return
- Dim OldState = _State
- If value = LoadState.Finished AndAlso Settings.Get(Of Boolean)("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000))
- _State = value
- If value = LoadState.Canceled Then CancellationTokenSource?.Cancel()
- Logger.Log($"加载器 {[GetType].Name} {Name} 状态改变:{value}", If(value = LoadState.Failed, LogLevel.Error, LogLevel.Info))
- '实现 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 Return
- 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, _Progress)
- Case Else
- Return 1
- End Select
- End Get
- Set(value As Double)
- If _Progress = value Then Return
- Dim OldValue = _Progress
- _Progress = value
- RaiseEvent ProgressChanged(value, OldValue)
- End Set
- End Property
- Private _Progress As Double = -1
- Public Event ProgressChanged(NewProgress As Double, OldProgress As Double) Implements ILoadingTrigger.ProgressChanged
- ''' <summary>
- ''' 创建一个 ProgressProvider,在其进度改变时,并将其进度单向映射到此加载器上。
- ''' 这是 Loader 对新框架的兼容层。
- ''' </summary>
- Public Function CreateSyncProgressProvider(Optional FromPercentage As Double = 0, Optional ToPercentage As Double = 1) As ProgressProvider
- Dim Provider As New ProgressProvider
- AddHandler Provider.ProgressChanged,
- Sub(ChangedProgress)
- Dim RawProgress = Provider.GetRaw()
- Progress = FromPercentage + (RawProgress.actual + RawProgress.skiped) * (ToPercentage - FromPercentage)
- End Sub
- Return Provider
- End Function
- ''' <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 Cancel()
- Public MustOverride Sub Failed(Ex As Exception)
- Private CancellationTokenSource As CancellationTokenSource
- ''' <summary>
- ''' 创建一个随此加载器取消的取消令牌;令牌取消时也会调用此加载器的 Cancel。
- ''' 这是 Loader 对新框架的兼容层。
- ''' </summary>
- Public Function CreateCancellationToken() As CancellationToken
- SyncLock LockState
- If CancellationTokenSource Is Nothing OrElse CancellationTokenSource.IsCancellationRequested Then
- CancellationTokenSource = New CancellationTokenSource
- CancellationTokenSource.Token.Register(AddressOf Cancel)
- End If
- If State = LoadState.Canceled Then CancellationTokenSource.Cancel()
- Return CancellationTokenSource.Token
- End SyncLock
- End Function
- '等待结束
- 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,
- Optional c As CancellationToken = Nothing, Optional p As ProgressProvider = Nothing)
- Start(Input, IsForceRestart)
- Do While State = LoadState.Loading
- If LoaderToSyncProgress IsNot Nothing Then LoaderToSyncProgress.Progress = Progress
- p?.Set(Progress)
- c.ThrowIfCancellationRequested()
- Thread.Sleep(10)
- Loop
- If State = LoadState.Finished Then
- Return
- ElseIf State = LoadState.Canceled Then
- Throw New OperationCanceledException("加载器执行已中断")
- 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
- Return
- ElseIf State = LoadState.Canceled Then
- Throw New OperationCanceledException("加载器执行已中断")
- 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 TIn, TOut)
- Inherits LoaderBase
- '线程设定
- Protected Friend ThreadPriority As ThreadPriority
- '执行事件
- Protected Friend LoadDelegate As Action(Of LoaderTask(Of TIn, TOut))
- Protected Friend InputDelegate As Func(Of Object)
- '状态指示
- ''' <summary>
- ''' 当前执行线程是否应当中断。只应用在加载器的工作线程中判断,不可跨线程调用。
- ''' </summary>
- Public ReadOnly Property IsCanceled As Boolean
- Get
- Return IsCanceledWithThread(Thread.CurrentThread)
- End Get
- End Property
- ''' <summary>
- ''' 当前执行线程是否应当中断。需要手动提供加载器线程,用于需要跨线程检查的情况。
- ''' </summary>
- Public Function IsCanceledWithThread(Thread As Thread) As Boolean
- Return LastRunningThread Is Nothing OrElse Not ReferenceEquals(Thread, LastRunningThread) OrElse State = LoadState.Canceled
- End Function
- ''' <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 TIn = Nothing
- Public Output As TOut = Nothing
- '获取输入
- Public Function StartGetInput(Optional Input As TIn = Nothing, Optional InputDelegate As Func(Of TIn) = Nothing) As TIn 'InputDelegate 参数存在匿名调用
- If InputDelegate Is Nothing Then InputDelegate = Me.InputDelegate
- Dim NewInput As TIn = 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 TIn, Optional IsForceRestart As Boolean = False, Optional IgnoreReloadTimeout As Boolean = False) As Boolean
- '获取输入
- Try
- Input = StartGetInput(Input)
- Catch ex As Exception
- Logger.Error(ex, $"加载输入获取失败({Name})", LogBehavior.Toast)
- [Error] = ex
- SyncLock LockState
- State = LoadState.Failed
- End SyncLock
- End Try
- '检验输入以确定情况
- 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 GetTimeMs() - 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 TriggerThreadInterrupt()
- Me.Input = Input
- SyncLock LockState
- State = LoadState.Loading
- Progress = -1
- End SyncLock
- Else
- Return
- End If
- LastRunningThread = New Thread(
- Sub()
- Try
- IsForceRestarting = IsForceRestart
- Logger.Trace(Function() $"加载线程 {Name} 已{If(IsForceRestarting, "强制", "")}启动")
- LoadDelegate(Me)
- If IsCanceled Then
- Logger.Warn($"加载线程 {Name} 已中断但线程正常运行至结束,输出被弃用(最新线程:{If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId)})")
- Return
- End If
- Logger.Trace(Function() $"加载线程 {Name} 已完成")
- RaisePreviewFinish()
- State = LoadState.Finished
- LastFinishedTime = GetTimeMs() '未中断,本次输出有效
- Catch ex As Exception
- If ex.IsCanceled Then
- '如果线程是因为判断到 IsCanceled 而提前中止,则代表已有新线程被重启,此时不应当改为 Canceled
- '如果线程是在没有 IsCanceled 时手动引发了取消异常,则代表没有重启线程,这通常代表用户手动取消,应当改为 Canceled
- Logger.Trace(ex, $"加载线程 {Name} 已取消,当前已完成 {Math.Round(Progress * 100)}%")
- If Not IsCanceled Then State = LoadState.Canceled
- Else
- Failed(ex)
- End If
- End Try
- End Sub) With {.Name = "L/" & Name, .Priority = ThreadPriority}
- Try
- LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsCanceled
- Catch ex As ThreadStateException '若遇到偶发的 “线程正在运行或被终止”,则等待后重试
- Thread.Sleep(500)
- LastRunningThread.Start()
- End Try
- End Sub
- Public Overrides Sub Failed(ex As Exception)
- [Error] = ex
- SyncLock LockState
- If IsCanceled OrElse State >= LoadState.Finished Then Return
- State = LoadState.Failed
- End SyncLock
- Logger.Warn(ex, $"加载线程 {Name} 出错,已完成 {Math.Round(Progress * 100)}%")
- TriggerThreadInterrupt()
- End Sub
- Public Overrides Sub Cancel()
- SyncLock LockState
- If State <> LoadState.Loading Then Return
- State = LoadState.Canceled
- End SyncLock
- TriggerThreadInterrupt()
- End Sub
- Private Sub TriggerThreadInterrupt()
- If LastRunningThread Is Nothing Then Return
- If LastRunningThread.IsAlive Then
- LastRunningThread.Interrupt()
- Logger.Trace(Function() $"加载线程 {Name} 已中断")
- End If
- LastRunningThread = Nothing
- End Sub
- Public Sub New()
- '仅仅是为了避免一些智障报错(继承类必须重写 New 的情况)
- End Sub
- Public Sub New(Name As String, LoadDelegate As Action(Of LoaderTask(Of TIn, TOut)), Optional InputDelegate As Func(Of TIn) = Nothing, Optional Priority As ThreadPriority = ThreadPriority.Normal)
- Me.Name = Name
- Me.LoadDelegate = LoadDelegate
- Me.InputDelegate = InputDelegate
- ThreadPriority = Priority
- End Sub
- End Class
- ''' <summary>
- ''' 新 Worker 对老 Loader 的兼容包装类。
- ''' </summary>
- Public Class LoaderWorker
- Inherits LoaderBase
- Public WithEvents Worker As IWorker
- Public Sub New(Name As String, Worker As IWorker)
- Me.Name = Name
- Me.Worker = Worker
- AddHandler Worker.Progress.ProgressChanged, Sub(Raw) Progress = Raw.actual + Raw.skiped
- End Sub
- Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
- SyncLock LockState
- State = LoadState.Loading
- End SyncLock
- Logger.Trace(Function() $"Worker {Name} 已启动")
- IsForceRestarting = IsForceRestart
- Worker.Start()
- End Sub
- Private Sub Worker_Succeeded() Handles Worker.Succeeded
- Logger.Trace(Function() $"Worker {Name} 已完成")
- RaisePreviewFinish()
- SyncLock LockState
- State = LoadState.Finished
- End SyncLock
- End Sub
- Public Overrides Sub Cancel() Handles Worker.Canceled
- SyncLock LockState
- If State <> LoadState.Loading Then Return
- State = LoadState.Canceled
- End SyncLock
- Logger.Trace(Function() $"Worker {Name} 已取消,当前已完成 {Math.Round(Progress * 100)}%")
- Worker.Cancel()
- End Sub
- Public Overrides Sub Failed(Ex As Exception) Handles Worker.Failed
- SyncLock LockState
- If State >= LoadState.Finished Then Return
- State = LoadState.Failed
- End SyncLock
- Logger.Warn(Ex, $"Worker {Name} 出错,已完成 {Math.Round(Progress * 100)}%")
- [Error] = Ex
- Worker.Cancel()
- 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
- Return
- 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
- RunInThread(AddressOf Update)
- End Sub
- Public Overrides Sub Cancel()
- '改变状态
- SyncLock LockState
- If State = LoadState.Loading OrElse State = LoadState.Waiting Then
- State = LoadState.Canceled
- Else
- Return
- End If
- End SyncLock
- RunInThread(
- Sub()
- '中断加载器
- For Each Loader In Loaders
- Loader.Cancel()
- Next
- End Sub)
- End Sub
- Public Overrides Sub Failed(Ex As Exception)
- [Error] = Ex '先设置错误再调整状态,防止父加载器获取不到异常
- SyncLock LockState
- If State >= LoadState.Finished Then Return
- State = LoadState.Failed
- End SyncLock
- For Each Loader In Loaders
- Loader.Cancel()
- Next
- FrmMain.BtnExtraDownload.ShowRefresh()
- 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.Canceled
- '被中断,这个任务也中断
- Cancel()
- Case LoadState.Failed
- '完蛋,出错了
- Failed(New Exception(Loader.Name & "失败", Loader.Error))
- End Select
- End Sub
- ''' <summary>
- ''' 触发一次更新,以启动新加载器或完成。
- ''' </summary>
- Private Sub Update()
- If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Canceled Then Return
- Dim IsFinished As Boolean = True
- Dim Blocked As Boolean = False
- Dim Input As Object = Me.Input
- For Each Loader In Loaders
- Select Case Loader.State
- Case LoadState.Finished
- '检查 LoaderTask 是否需要重启
- If GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderTask(Of ,))) Then
- If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
- Logger.Info($"由于输入条件变更,重启已完成的加载器 {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
- '检查 LoaderTask 是否需要重启
- If GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderTask(Of ,))) Then
- If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
- Logger.Warn($"由于输入条件变更,重启进行中的加载器 {Loader.Name}")
- GoTo Restart
- End If
- End If
- '已经有正在加载中的了,不需要再启动了
- IsFinished = False
- Blocked = True
- Case Else
- Restart:
- '未启动,则启动加载器
- IsFinished = False
- If Blocked Then Continue For
- If Input IsNot Nothing Then
- '若输入类型与下一个加载器相同才继续
- If GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderTask(Of ,))) OrElse GeneralUtils.IsGenericInstanceOf(Loader, GetType(LoaderCombo(Of ))) Then
- Loader.Start(If(Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IsForceRestarting)
- ElseIf TypeOf Loader Is LoaderWorker Then
- Loader.Start(IsForceRestart:=IsForceRestarting)
- ElseIf TypeOf Loader Is LoaderDownload Then
- Loader.Start(If(TypeOf Input Is List(Of NetFile), Input, Nothing), IsForceRestarting)
- Else
- Throw New Exception("未知的加载器类型(" & Loader.GetType.Name & ")")
- End If
- Else
- Loader.Start(IsForceRestart:=IsForceRestarting)
- End If
- '阻止继续
- If Loader.Block Then Blocked = 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 GeneralUtils.IsGenericInstanceOf(SubLoader, GetType(LoaderCombo(Of ))) 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 LoaderTaskbar As New ConcurrentList(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)
- LoaderTaskbar.Add(Loader)
- Logger.Info($"{Loader.Name} 已加入任务列表")
- End Sub
- Public Sub LoaderTaskbarProgressRefresh()
- Try
- Dim NewState As Shell.TaskbarItemProgressState
- Dim NewProgress As Double = LoaderTaskbarProgressGet()
- '若单个任务已中止,或全部任务已完成,则刷新并移除
- For Each Task In LoaderTaskbar
- If LoaderTaskbar.All(Function(l) l.State <> LoadState.Loading) OrElse
- (Task.State = LoadState.Waiting OrElse Task.State = LoadState.Canceled) Then
- FrmSpeedLeft?.TaskRefresh(Task)
- LoaderTaskbar.Remove(Task)
- Logger.Info($"{Task.Name} 已移出任务列表")
- End If
- Next
- '更新平滑后的进度
- If NewProgress <= 0 OrElse NewProgress >= 1 OrElse LoaderTaskbarProgress > NewProgress Then
- LoaderTaskbarProgress = NewProgress
- Else
- LoaderTaskbarProgress = LoaderTaskbarProgress * 0.9 + NewProgress * 0.1
- End If
- RunInUi(Sub() FrmMain.BtnExtraDownload.Progress = LoaderTaskbarProgress)
- '更新任务栏信息
- If Not LoaderTaskbar.Any() 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
- Logger.Error(ex, "刷新任务栏进度显示失败")
- End Try
- End Sub
- Public Function LoaderTaskbarProgressGet() As Double
- Try
- If Not LoaderTaskbar.Any Then Return 1
- Return LoaderTaskbar.Select(Function(l) l.Progress).Average().Clamp(0, 1)
- Catch ex As Exception
- Logger.Error(ex, "获取任务栏进度出错")
- 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 Value As New LoaderFolderDictionaryEntry With {.FolderPath = FolderPath & ExtraPath, .LastCheckTime = Nothing}
- Try
- '获取数据
- Dim FolderInfo = DirectoryUtils.GetInfo(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
- Logger.Warn(ex, "文件夹加载器启动检测出错")
- End Try
- '写入检查数据
- 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
|