ModLoader.vb 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. Public Module ModLoader
  2. '各类加载器
  3. ''' <summary>
  4. ''' 加载器的统一基类。
  5. ''' </summary>
  6. Public MustInherit Class LoaderBase
  7. Implements ILoadingTrigger
  8. Public ReadOnly Property IsLoader As Boolean = True Implements ILoadingTrigger.IsLoader
  9. '基础属性
  10. ''' <summary>
  11. ''' 加载器的标识编号。
  12. ''' </summary>
  13. Public Uuid As Integer = GetUuid()
  14. ''' <summary>
  15. ''' 加载器的名称。
  16. ''' </summary>
  17. Public Name As String = "未命名任务 " & Uuid & "#"
  18. ''' <summary>
  19. ''' 用于状态改变检测的同步锁。
  20. ''' </summary>
  21. Public ReadOnly LockState As New Object
  22. ''' <summary>
  23. ''' 父加载器。
  24. ''' </summary>
  25. Public Parent As LoaderBase = Nothing
  26. ''' <summary>
  27. ''' 最上级的加载器。
  28. ''' </summary>
  29. Public ReadOnly Property RealParent As LoaderBase
  30. Get
  31. Try
  32. RealParent = Parent
  33. While RealParent IsNot Nothing AndAlso RealParent.Parent IsNot Nothing
  34. RealParent = RealParent.Parent
  35. End While
  36. Catch ex As Exception
  37. Logger.Error(ex, $"获取父加载器失败({Name})")
  38. Return Nothing
  39. End Try
  40. End Get
  41. End Property
  42. Public Overridable Sub InitParent(Parent As LoaderBase)
  43. Me.Parent = Parent
  44. End Sub
  45. '事件
  46. ''' <summary>
  47. ''' 当状态改变时,在工作线程触发代码。在添加事件后,必须将 HasOnStateChangedThread 设为 True。
  48. ''' </summary>
  49. Public Event OnStateChangedThread(Loader As LoaderBase, NewState As LoadState, OldState As LoadState)
  50. Public HasOnStateChangedThread As Boolean = False
  51. ''' <summary>
  52. ''' 当状态改变时,在 UI 线程触发代码。
  53. ''' </summary>
  54. Public Event OnStateChangedUi(Loader As LoaderBase, NewState As LoadState, OldState As LoadState)
  55. ''' <summary>
  56. ''' 简易的在 UI 线程添加触发事件的方式。主要用于在新建 Loader 时直接使用 With 绑定事件,以及进行老代码兼容。
  57. ''' </summary>
  58. Public WriteOnly Property OnStateChanged As Action(Of LoaderBase)
  59. Set(value As Action(Of LoaderBase))
  60. AddHandler OnStateChangedUi, Sub(Loader As LoaderBase, NewState As LoadState, OldState As LoadState) value(Loader)
  61. End Set
  62. End Property
  63. ''' <summary>
  64. ''' 在加载器目标事件执行完成,加载器状态即将变为 Finish 时调用。可以视为扩展加载器目标事件。
  65. ''' </summary>
  66. Public Event PreviewFinish(Loader As LoaderBase)
  67. Protected Sub RaisePreviewFinish()
  68. RaiseEvent PreviewFinish(Me)
  69. End Sub
  70. '状态监控
  71. ''' <summary>
  72. ''' 加载器的状态。
  73. ''' </summary>
  74. Public Property State As LoadState
  75. Get
  76. Return _State
  77. End Get
  78. Set(value As LoadState)
  79. If _State = value Then Return
  80. Dim OldState = _State
  81. If value = LoadState.Finished AndAlso Settings.Get(Of Boolean)("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000))
  82. _State = value
  83. Logger.Log($"加载器 {Name} 状态改变:{value}", If(value = LoadState.Failed, LogLevel.Error, LogLevel.Info))
  84. '实现 ILoadingTrigger 接口与 OnStateChanged 回调
  85. RunInUi(
  86. Sub()
  87. Select Case value
  88. Case LoadState.Loading
  89. LoadingState = MyLoading.MyLoadingState.Run
  90. Case LoadState.Failed
  91. LoadingState = MyLoading.MyLoadingState.Error
  92. Case Else
  93. LoadingState = MyLoading.MyLoadingState.Stop
  94. End Select
  95. RaiseEvent OnStateChangedUi(Me, value, OldState)
  96. End Sub)
  97. If HasOnStateChangedThread Then RunInThread(Sub() RaiseEvent OnStateChangedThread(Me, value, OldState))
  98. End Set
  99. End Property
  100. Private _State As LoadState = LoadState.Waiting
  101. Public Property LoadingState As MyLoading.MyLoadingState Implements ILoadingTrigger.LoadingState
  102. Get
  103. Return _LoadingState
  104. End Get
  105. Set(value As MyLoading.MyLoadingState)
  106. If _LoadingState = value Then Return
  107. Dim OldState = _LoadingState
  108. _LoadingState = value
  109. RaiseEvent LoadingStateChanged(value, OldState)
  110. End Set
  111. End Property
  112. Private _LoadingState As MyLoading.MyLoadingState = MyLoading.MyLoadingState.Stop
  113. Public Event LoadingStateChanged(NewState As MyLoading.MyLoadingState, OldState As MyLoading.MyLoadingState) Implements ILoadingTrigger.LoadingStateChanged
  114. ''' <summary>
  115. ''' 若加载器出错,可提供给外部参考的异常。
  116. ''' </summary>
  117. Public Property [Error] As Exception = Nothing
  118. ''' <summary>
  119. ''' 使用 LoaderCombo 加载时,该任务是否会阻碍后续任务的进行。
  120. ''' </summary>
  121. Public Block As Boolean = True
  122. ''' <summary>
  123. ''' 该加载器是否显示在列表中。
  124. ''' </summary>
  125. Public Show As Boolean = True
  126. ''' <summary>
  127. ''' 当前加载器是否由 IsForceRestart 强制调起。
  128. ''' 这个属性自身不会干任何事,而是提供给加载器执行的函数,使得加载器调用另一个加载器时,可以继承强制重启属性。
  129. ''' </summary>
  130. Public IsForceRestarting As Boolean = False
  131. '进度监控
  132. ''' <summary>
  133. ''' 加载器的执行进度,为 0 至 1 的小数。
  134. ''' </summary>
  135. Public Overridable Property Progress As Double
  136. Get
  137. Select Case State
  138. Case LoadState.Waiting
  139. Return 0
  140. Case LoadState.Loading
  141. Return If(_Progress = -1, 0, _Progress)
  142. Case Else
  143. Return 1
  144. End Select
  145. End Get
  146. Set(value As Double)
  147. If _Progress = value Then Return
  148. Dim OldValue = _Progress
  149. _Progress = value
  150. RaiseEvent ProgressChanged(value, OldValue)
  151. End Set
  152. End Property
  153. Private _Progress As Double = -1
  154. Public Event ProgressChanged(NewProgress As Double, OldProgress As Double) Implements ILoadingTrigger.ProgressChanged
  155. ''' <summary>
  156. ''' 计算总进度时的权重。它应该为预计时间(秒)。
  157. ''' </summary>
  158. Public Property ProgressWeight As Double = 1
  159. '状态变化
  160. Public MustOverride Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
  161. Public MustOverride Sub Interrupt()
  162. Public MustOverride Sub Failed(Ex As Exception)
  163. '等待结束
  164. Public Const WaitForExitTimeoutMessage As String = "等待加载器执行超时。"
  165. ''' <summary>
  166. ''' 无限期地等待加载器完成,直到结束或抛出异常。若加载器尚未开始,则会开始执行。
  167. ''' </summary>
  168. Public Sub WaitForExit(Optional Input As Object = Nothing, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional IsForceRestart As Boolean = False)
  169. Start(Input, IsForceRestart)
  170. Do While State = LoadState.Loading
  171. If LoaderToSyncProgress IsNot Nothing Then LoaderToSyncProgress.Progress = Progress
  172. Thread.Sleep(10)
  173. Loop
  174. If State = LoadState.Finished Then
  175. Return
  176. ElseIf State = LoadState.Interrupted Then
  177. Throw New ThreadInterruptedException("加载器执行已中断。")
  178. ElseIf IsNothing([Error]) Then
  179. Throw New Exception("未知错误!")
  180. Else
  181. Throw New Exception([Error].Message, [Error]) '保留调用堆栈,同时不影响信息输出与单元测试
  182. End If
  183. End Sub
  184. ''' <summary>
  185. ''' 等待加载器完成,直到结束、抛出异常或超时。若加载器尚未开始,则会开始执行。
  186. ''' </summary>
  187. ''' <param name="Timeout">等待的超时时间,以毫秒为单位。</param>
  188. ''' <param name="TimeoutMessage">若执行超时,将会抛出的异常信息。</param>
  189. 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)
  190. Start(Input, IsForceRestart)
  191. Do While State = LoadState.Loading
  192. If LoaderToSyncProgress IsNot Nothing Then LoaderToSyncProgress.Progress = Progress
  193. Thread.Sleep(10)
  194. Timeout -= 10
  195. If Timeout < 0 Then Throw New TimeoutException(TimeoutMessage)
  196. Loop
  197. If State = LoadState.Finished Then
  198. Return
  199. ElseIf State = LoadState.Interrupted Then
  200. Throw New ThreadInterruptedException("加载器执行已中断。")
  201. ElseIf IsNothing([Error]) Then
  202. Throw New Exception("未知错误!")
  203. Else
  204. Throw [Error]
  205. End If
  206. End Sub
  207. '相同重载
  208. Public Overrides Function Equals(obj As Object) As Boolean
  209. Dim base = TryCast(obj, LoaderBase)
  210. Return base IsNot Nothing AndAlso Uuid = base.Uuid
  211. End Function
  212. End Class
  213. ''' <summary>
  214. ''' 用于异步执行并监控单一函数的加载器。
  215. ''' </summary>
  216. Public Class LoaderTask(Of TIn, TOut)
  217. Inherits LoaderBase
  218. '线程设定
  219. Protected Friend ThreadPriority As ThreadPriority
  220. '执行事件
  221. Protected Friend LoadDelegate As Action(Of LoaderTask(Of TIn, TOut))
  222. Protected Friend InputDelegate As Func(Of Object)
  223. '状态指示
  224. ''' <summary>
  225. ''' 当前执行线程是否应当中断。只应用在加载器的工作线程中判断,不可跨线程调用。
  226. ''' </summary>
  227. Public ReadOnly Property IsInterrupted As Boolean
  228. Get
  229. Return IsInterruptedWithThread(Thread.CurrentThread)
  230. End Get
  231. End Property
  232. ''' <summary>
  233. ''' 当前执行线程是否应当中断。需要手动提供加载器线程,用于需要跨线程检查的情况。
  234. ''' </summary>
  235. Public Function IsInterruptedWithThread(Thread As Thread) As Boolean
  236. Return LastRunningThread Is Nothing OrElse Not ReferenceEquals(Thread, LastRunningThread) OrElse State = LoadState.Interrupted
  237. End Function
  238. ''' <summary>
  239. ''' 在输入相同时使用原有结果的超时,单位为毫秒。
  240. ''' </summary>
  241. Public ReloadTimeout As Integer = -1
  242. ''' <summary>
  243. ''' 上次完成加载时的时间。
  244. ''' </summary>
  245. Public LastFinishedTime As Long = 0
  246. ''' <summary>
  247. ''' 最后一次运行加载器的线程。可能为 Nothing,或线程已结束。
  248. ''' </summary>
  249. Public LastRunningThread As Thread = Nothing
  250. '输入输出
  251. Public Input As TIn = Nothing
  252. Public Output As TOut = Nothing
  253. '获取输入
  254. Public Function StartGetInput(Optional Input As TIn = Nothing, Optional InputDelegate As Func(Of TIn) = Nothing) As TIn 'InputDelegate 参数存在匿名调用
  255. If InputDelegate Is Nothing Then InputDelegate = Me.InputDelegate
  256. Dim NewInput As TIn = Nothing '若 InputType 不能为 Nothing,则会导致 Input Is Nothing 永远失败,因此需要额外判断
  257. If (Input Is Nothing OrElse (NewInput IsNot Nothing AndAlso Input.Equals(NewInput))) AndAlso InputDelegate IsNot Nothing Then
  258. RunInUiWait(Sub() Input = InputDelegate())
  259. End If
  260. Return Input
  261. End Function
  262. '代码执行
  263. Public Function ShouldStart(ByRef Input As TIn, Optional IsForceRestart As Boolean = False, Optional IgnoreReloadTimeout As Boolean = False) As Boolean
  264. '获取输入
  265. Try
  266. Input = StartGetInput(Input)
  267. Catch ex As Exception
  268. Logger.Error(ex, $"加载输入获取失败({Name})", LogBehavior.Toast)
  269. [Error] = ex
  270. SyncLock LockState
  271. State = LoadState.Failed
  272. End SyncLock
  273. End Try
  274. '检验输入以确定情况
  275. If IsForceRestart Then Return True '强制要求重启
  276. If ((Input Is Nothing) <> (Me.Input Is Nothing)) OrElse (Input IsNot Nothing AndAlso Not Input.Equals(Me.Input)) Then Return True '输入不同
  277. If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束…
  278. (IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse
  279. LastFinishedTime = 0 OrElse GetTimeMs() - LastFinishedTime < ReloadTimeout) Then '…且没有超时…
  280. Return False '…则不重试
  281. Else
  282. Return True '否则需要重启
  283. End If
  284. End Function
  285. Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
  286. '确认是否开始加载
  287. If ShouldStart(Input, IsForceRestart) Then
  288. '输入不同或失败,开始加载
  289. If State = LoadState.Loading Then TriggerThreadInterrupt()
  290. Me.Input = Input
  291. SyncLock LockState
  292. State = LoadState.Loading
  293. Progress = -1
  294. End SyncLock
  295. Else
  296. Return
  297. End If
  298. LastRunningThread = New Thread(
  299. Sub()
  300. Try
  301. IsForceRestarting = IsForceRestart
  302. Logger.Trace(Function() $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已{If(IsForceRestarting, "强制", "")}启动")
  303. LoadDelegate(Me)
  304. If IsInterrupted Then
  305. Logger.Warn($"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已中断但线程正常运行至结束,输出被弃用(最新线程:{If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId)})")
  306. Return
  307. End If
  308. Logger.Trace(Function() $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已完成")
  309. RaisePreviewFinish()
  310. State = LoadState.Finished
  311. LastFinishedTime = GetTimeMs() '未中断,本次输出有效
  312. Catch ex As CancelledException
  313. Logger.Trace(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发取消中断,已完成 {Math.Round(Progress * 100)}%")
  314. If Not IsInterrupted Then State = LoadState.Interrupted
  315. Catch ex As ThreadInterruptedException
  316. Logger.Trace(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发线程中断,已完成 {Math.Round(Progress * 100)}%")
  317. '如果线程是因为判断到 IsInterrupted 而提前中止,则代表已有新线程被重启,此时不应当改为 Interrupted
  318. '如果线程是在没有 IsInterrupted 时手动引发了 ThreadInterruptedException,则代表没有重启线程,这通常代表用户手动取消,应当改为 Interrupted
  319. If Not IsInterrupted Then State = LoadState.Interrupted
  320. Catch ex As Exception
  321. Failed(ex)
  322. End Try
  323. End Sub) With {.Name = "L/" & Name, .Priority = ThreadPriority}
  324. Try
  325. LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsInterrupted
  326. Catch ex As ThreadStateException '若遇到偶发的 “线程正在运行或被终止”,则等待后重试
  327. Thread.Sleep(500)
  328. LastRunningThread.Start()
  329. End Try
  330. End Sub
  331. Public Overrides Sub Failed(ex As Exception)
  332. [Error] = ex
  333. SyncLock LockState
  334. If IsInterrupted OrElse State >= LoadState.Finished Then Return
  335. State = LoadState.Failed
  336. End SyncLock
  337. Logger.Warn(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 出错,已完成 {Math.Round(Progress * 100)}%")
  338. TriggerThreadInterrupt()
  339. End Sub
  340. Public Overrides Sub Interrupt()
  341. SyncLock LockState
  342. If State <> LoadState.Loading Then Return
  343. State = LoadState.Interrupted
  344. End SyncLock
  345. TriggerThreadInterrupt()
  346. End Sub
  347. Private Sub TriggerThreadInterrupt()
  348. If LastRunningThread Is Nothing Then Return
  349. If LastRunningThread.IsAlive Then
  350. LastRunningThread.Interrupt()
  351. Logger.Trace(Function() $"加载线程 {Name} ({LastRunningThread.ManagedThreadId}) 已中断")
  352. End If
  353. LastRunningThread = Nothing
  354. End Sub
  355. Public Sub New()
  356. '仅仅是为了避免一些智障报错(继承类必须重写 New 的情况)
  357. End Sub
  358. 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)
  359. Me.Name = Name
  360. Me.LoadDelegate = LoadDelegate
  361. Me.InputDelegate = InputDelegate
  362. ThreadPriority = Priority
  363. End Sub
  364. End Class
  365. ''' <summary>
  366. ''' 支持多个加载器连续运作的复合加载器。
  367. ''' </summary>
  368. Public Class LoaderCombo(Of InputType)
  369. Inherits LoaderBase
  370. Public Overrides Property Progress() As Double
  371. Get
  372. Select Case State
  373. Case LoadState.Waiting
  374. Return 0
  375. Case LoadState.Loading
  376. Dim Total As Double = 0, Finished As Double = 0
  377. For Each Loader In Loaders
  378. Total += Loader.ProgressWeight
  379. Finished += Loader.ProgressWeight * Loader.Progress
  380. Next
  381. If Total = 0 Then Return 0
  382. Return Finished / Total
  383. Case Else
  384. Return 1
  385. End Select
  386. End Get
  387. Set(value As Double)
  388. Throw New Exception("多重加载器不支持设置进度")
  389. End Set
  390. End Property
  391. Public Loaders As New List(Of LoaderBase)
  392. Public Input As InputType
  393. Public Sub New(Name As String, Loaders As IEnumerable(Of LoaderBase))
  394. Me.Loaders.Clear()
  395. For Each Loader As LoaderBase In Loaders
  396. If Loader IsNot Nothing Then
  397. Me.Loaders.Add(Loader)
  398. AddHandler Loader.OnStateChangedThread, AddressOf SubTaskStateChanged
  399. Loader.HasOnStateChangedThread = True
  400. End If
  401. Next
  402. InitParent(Nothing)
  403. Me.Name = Name
  404. End Sub
  405. Public Overrides Sub InitParent(Parent As LoaderBase)
  406. Me.Parent = Parent
  407. For Each Loader In Loaders
  408. Loader.InitParent(Me)
  409. Next
  410. End Sub
  411. Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
  412. IsForceRestarting = IsForceRestart
  413. '改变状态
  414. SyncLock LockState
  415. If State = LoadState.Loading Then
  416. Return
  417. Else
  418. State = LoadState.Loading
  419. End If
  420. End SyncLock
  421. '启动加载
  422. Me.Input = Input
  423. If IsForceRestart Then
  424. For Each Loader In Loaders
  425. Loader.State = LoadState.Waiting
  426. Next
  427. End If
  428. RunInThread(AddressOf Update)
  429. End Sub
  430. Public Overrides Sub Interrupt()
  431. '改变状态
  432. SyncLock LockState
  433. If State = LoadState.Loading OrElse State = LoadState.Waiting Then
  434. State = LoadState.Interrupted
  435. Else
  436. Return
  437. End If
  438. End SyncLock
  439. RunInThread(
  440. Sub()
  441. '中断加载器
  442. For Each Loader In Loaders
  443. Loader.Interrupt()
  444. Next
  445. End Sub)
  446. End Sub
  447. Public Overrides Sub Failed(Ex As Exception)
  448. [Error] = Ex '先设置错误再调整状态,防止父加载器获取不到异常
  449. SyncLock LockState
  450. If State >= LoadState.Finished Then Return
  451. State = LoadState.Failed
  452. End SyncLock
  453. For Each Loader In Loaders
  454. Loader.Interrupt()
  455. Next
  456. FrmMain.BtnExtraDownload.ShowRefresh()
  457. End Sub
  458. ''' <summary>
  459. ''' 子任务状态变更。
  460. ''' </summary>
  461. Private Sub SubTaskStateChanged(Loader As LoaderBase, NewState As LoadState, OldState As LoadState)
  462. Select Case NewState
  463. Case LoadState.Loading
  464. '开始,啥都不干
  465. Case LoadState.Waiting
  466. '子加载器可能由于外部输入改变而暂时变为 Waiting,之后会立即重新启动
  467. '所以啥都不干就行
  468. Case LoadState.Finished
  469. '正常结束,触发刷新
  470. Update()
  471. Case LoadState.Interrupted
  472. '被中断,这个任务也中断
  473. Interrupt()
  474. Case LoadState.Failed
  475. '完蛋,出错了
  476. Failed(New Exception(Loader.Name & "失败", Loader.Error))
  477. End Select
  478. End Sub
  479. ''' <summary>
  480. ''' 触发一次更新,以启动新加载器或完成。
  481. ''' </summary>
  482. Private Sub Update()
  483. If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Interrupted Then Return
  484. Dim IsFinished As Boolean = True
  485. Dim Blocked As Boolean = False
  486. Dim Input As Object = Me.Input
  487. For Each Loader In Loaders
  488. Select Case Loader.State
  489. Case LoadState.Finished
  490. '检查是否需要重启
  491. If Loader.GetType.Name.StartsWithF("LoaderTask") Then '类型名后面带有泛型,必须用 StartsWith
  492. If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
  493. Logger.Info($"由于输入条件变更,重启已完成的加载器 {Loader.Name}")
  494. GoTo Restart
  495. End If
  496. '更新下一个加载器的输入
  497. Input = CType(Loader, Object).Output
  498. End If
  499. '如果不让继续启动,且已有加载器正在加载中,就不继续启动
  500. If Loader.Block AndAlso Not IsFinished Then Blocked = True
  501. Case LoadState.Loading
  502. '检查是否需要重启
  503. If Loader.GetType.Name.StartsWithF("LoaderTask") Then
  504. If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
  505. Logger.Warn($"由于输入条件变更,重启进行中的加载器 {Loader.Name}")
  506. GoTo Restart
  507. End If
  508. End If
  509. '已经有正在加载中的了,不需要再启动了
  510. IsFinished = False
  511. Blocked = True
  512. Case Else
  513. Restart:
  514. '未启动,则启动加载器
  515. IsFinished = False
  516. If Blocked Then Continue For
  517. If Input IsNot Nothing Then
  518. '若输入类型与下一个加载器相同才继续
  519. Dim LoaderType As String = Loader.GetType.Name
  520. If LoaderType.StartsWithF("LoaderTask") OrElse LoaderType.StartsWithF("LoaderCombo") Then
  521. Loader.Start(If(Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IsForceRestarting)
  522. ElseIf LoaderType.StartsWithF("LoaderDownload") Then
  523. Loader.Start(If(TypeOf Input Is List(Of NetFile), Input, Nothing), IsForceRestarting)
  524. Else
  525. Throw New Exception("未知的加载器类型(" & LoaderType & ")")
  526. End If
  527. Else
  528. Loader.Start(IsForceRestart:=IsForceRestarting)
  529. End If
  530. '阻止继续
  531. If Loader.Block Then Blocked = True
  532. End Select
  533. Next
  534. If IsFinished Then
  535. '顺利完成,贼棒
  536. RaisePreviewFinish()
  537. State = LoadState.Finished
  538. FrmMain.BtnExtraDownload.ShowRefresh()
  539. End If
  540. End Sub
  541. ''' <summary>
  542. ''' 获得最底层的,应被显示给用户的加载器列表,并追加于 List。
  543. ''' </summary>
  544. Public Shared Sub GetLoaderList(Loader As Object, ByRef List As List(Of LoaderBase), Optional RequireShow As Boolean = True)
  545. For Each SubLoader In Loader.Loaders
  546. If SubLoader.Show OrElse Not RequireShow Then List.Add(SubLoader)
  547. If SubLoader.GetType.Name.StartsWithF("LoaderCombo") Then GetLoaderList(SubLoader, List)
  548. Next
  549. End Sub
  550. ''' <summary>
  551. ''' 获得最底层的,应被显示给用户的加载器列表,并追加于 List。
  552. ''' </summary>
  553. Public Sub GetLoaderList(ByRef List As List(Of LoaderBase), Optional RequireShow As Boolean = True)
  554. GetLoaderList(Me, List, RequireShow)
  555. End Sub
  556. ''' <summary>
  557. ''' 获得最底层的,应被显示给用户的加载器列表。
  558. ''' </summary>
  559. Public Function GetLoaderList(Optional RequireShow As Boolean = True) As List(Of LoaderBase)
  560. Dim List As New List(Of LoaderBase)
  561. GetLoaderList(List, RequireShow)
  562. Return List
  563. End Function
  564. End Class
  565. '任务栏进度条
  566. Public LoaderTaskbar As New ConcurrentList(Of LoaderBase)
  567. Public LoaderTaskbarProgress As Double = 0 '平滑后的进度
  568. Private LoaderTaskbarProgressLast As Shell.TaskbarItemProgressState = Shell.TaskbarItemProgressState.None
  569. Public Sub LoaderTaskbarAdd(Of T)(Loader As LoaderCombo(Of T))
  570. If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRemove(Loader)
  571. LoaderTaskbar.Add(Loader)
  572. Logger.Info($"{Loader.Name} 已加入任务列表")
  573. End Sub
  574. Public Sub LoaderTaskbarProgressRefresh()
  575. Try
  576. Dim NewState As Shell.TaskbarItemProgressState
  577. Dim NewProgress As Double = LoaderTaskbarProgressGet()
  578. '若单个任务已中止,或全部任务已完成,则刷新并移除
  579. For Each Task In LoaderTaskbar
  580. If LoaderTaskbar.All(Function(l) l.State <> LoadState.Loading) OrElse
  581. (Task.State = LoadState.Waiting OrElse Task.State = LoadState.Interrupted) Then
  582. FrmSpeedLeft?.TaskRefresh(Task)
  583. LoaderTaskbar.Remove(Task)
  584. Logger.Info($"{Task.Name} 已移出任务列表")
  585. End If
  586. Next
  587. '更新平滑后的进度
  588. If NewProgress <= 0 OrElse NewProgress >= 1 OrElse LoaderTaskbarProgress > NewProgress Then
  589. LoaderTaskbarProgress = NewProgress
  590. Else
  591. LoaderTaskbarProgress = LoaderTaskbarProgress * 0.9 + NewProgress * 0.1
  592. End If
  593. RunInUi(Sub() FrmMain.BtnExtraDownload.Progress = LoaderTaskbarProgress)
  594. '更新任务栏信息
  595. If Not LoaderTaskbar.Any() OrElse LoaderTaskbarProgress = 1 Then
  596. NewState = Shell.TaskbarItemProgressState.None
  597. ElseIf LoaderTaskbarProgress < 0.015 Then
  598. NewState = Shell.TaskbarItemProgressState.Indeterminate
  599. Else
  600. NewState = Shell.TaskbarItemProgressState.Normal
  601. FrmMain.TaskbarItemInfo.ProgressValue = LoaderTaskbarProgress
  602. End If
  603. If LoaderTaskbarProgressLast <> NewState Then
  604. LoaderTaskbarProgressLast = NewState
  605. FrmMain.TaskbarItemInfo.ProgressState = NewState
  606. FrmMain.BtnExtraDownload.ShowRefresh()
  607. End If
  608. Catch ex As Exception
  609. Logger.Error(ex, "刷新任务栏进度显示失败")
  610. End Try
  611. End Sub
  612. Public Function LoaderTaskbarProgressGet() As Double
  613. Try
  614. If Not LoaderTaskbar.Any Then Return 1
  615. Return LoaderTaskbar.Select(Function(l) l.Progress).Average().Clamp(0, 1)
  616. Catch ex As Exception
  617. Logger.Error(ex, "获取任务栏进度出错")
  618. Return 0.5
  619. End Try
  620. End Function
  621. '文件夹刷新类委托
  622. Private LoaderFolderDictionary As New Dictionary(Of LoaderBase, LoaderFolderDictionaryEntry)
  623. Private Structure LoaderFolderDictionaryEntry
  624. Public LastCheckTime As Date?
  625. Public FolderPath As String
  626. Public Overrides Function Equals(obj As Object) As Boolean
  627. If TypeOf obj IsNot LoaderFolderDictionaryEntry Then Return False
  628. Dim entry = DirectCast(obj, LoaderFolderDictionaryEntry)
  629. Return EqualityComparer(Of Date?).Default.Equals(LastCheckTime, entry.LastCheckTime) AndAlso
  630. FolderPath = entry.FolderPath
  631. End Function
  632. End Structure
  633. Public Enum LoaderFolderRunType
  634. RunOnUpdated
  635. ForceRun
  636. UpdateOnly
  637. End Enum
  638. ''' <summary>
  639. ''' 执行以文件夹检测作为输入的加载器。加载器需以文件夹路径为输入值。
  640. ''' 返回是否执行了加载器。
  641. ''' </summary>
  642. ''' <param name="ExtraPath">用于检查文件夹修改的额外路径。该路径不会传入加载器。</param>
  643. 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
  644. Dim Value As New LoaderFolderDictionaryEntry With {.FolderPath = FolderPath & ExtraPath, .LastCheckTime = Nothing}
  645. Try
  646. '获取数据
  647. Dim FolderInfo = DirectoryUtils.GetInfo(FolderPath & ExtraPath)
  648. Value.LastCheckTime = If(FolderInfo.Exists, GetActualLastWriteTimeUtc(FolderInfo, MaxDepth), DirectCast(Nothing, Date?))
  649. '如果已经检查过,则跳过
  650. If Type = LoaderFolderRunType.RunOnUpdated AndAlso LoaderFolderDictionary.ContainsKey(Loader) Then
  651. If FolderInfo.Exists Then
  652. If LoaderFolderDictionary(Loader).LastCheckTime IsNot Nothing AndAlso
  653. Value.Equals(LoaderFolderDictionary(Loader)) Then Return False
  654. Else
  655. If LoaderFolderDictionary(Loader).LastCheckTime Is Nothing Then Return False
  656. End If
  657. End If
  658. Catch ex As Exception
  659. Logger.Warn(ex, "文件夹加载器启动检测出错")
  660. End Try
  661. '写入检查数据
  662. LoaderFolderDictionary(Loader) = Value
  663. '开始检查
  664. If Type = LoaderFolderRunType.UpdateOnly Then Return False
  665. If WaitForExit Then
  666. Loader.WaitForExit(FolderPath, IsForceRestart:=True)
  667. Else
  668. Loader.Start(FolderPath, IsForceRestart:=True)
  669. End If
  670. Return True
  671. End Function
  672. Private Function GetActualLastWriteTimeUtc(FolderInfo As DirectoryInfo, MaxDepth As Integer) As Date
  673. Dim Time As Date = FolderInfo.LastWriteTimeUtc
  674. If MaxDepth > 0 Then
  675. For Each Folder In FolderInfo.EnumerateDirectories
  676. Dim FolderTime As Date = GetActualLastWriteTimeUtc(Folder, MaxDepth - 1)
  677. If FolderTime > Time Then Time = FolderTime
  678. Next
  679. End If
  680. Return Time
  681. End Function
  682. End Module