ModLoader.vb 33 KB

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