ModLoader.vb 33 KB

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