ModEvent.vb 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. Imports System.Collections.ObjectModel
  2. Public Module ModEvent
  3. #Region "事件执行方法"
  4. ''' <summary>
  5. ''' 供外部调用的自定义事件执行接口,会在新建工作线程中运行逻辑。
  6. ''' </summary>
  7. ''' <param name="SingleEventType">xaml中的EventType</param>
  8. ''' <param name="SingleEventData">xaml中的EventData</param>
  9. ''' <param name="EventCollection">xaml中的CustomEvents</param>
  10. Public Sub ProcessCustomEvents(SingleEventType As String, SingleEventData As String, EventCollection As CustomEventCollection)
  11. RunInNewThread(
  12. Sub()
  13. Try
  14. '先执行单个事件
  15. StartCustomEvent(SingleEventType, SingleEventData)
  16. '再挨个执行事件集合中的事件
  17. If EventCollection IsNot Nothing Then
  18. For Each AnEvent In RunInUiWait(Function() EventCollection.DeepCloneToStructureList())
  19. StartCustomEvent(AnEvent.EventType, AnEvent.EventData)
  20. Next
  21. End If
  22. Catch ex As Exception
  23. Log(ex, "自定义事件执行器发生异常", LogLevel.Feedback)
  24. End Try
  25. End Sub, "CustomEventProcessor " & GetUuid())
  26. End Sub
  27. ''' <summary>
  28. ''' 对当前线程阻塞地调用自定义事件处理器,接受来自 xaml 原始输入作为参数。
  29. ''' </summary>
  30. Private Sub StartCustomEvent(Type As String, DataRaw As String)
  31. If String.IsNullOrEmpty(Type) Then Exit Sub
  32. Dim Data = If(DataRaw Is Nothing, {""}, DataRaw.Split("|"))
  33. Log("[Control] 执行自定义事件:" & Type & ", " & Join(Data, ", "))
  34. '检测是否存在对应事件
  35. If Not CustomEventProcessorsDict.ContainsKey(Type) Then
  36. MyMsgBox($"未知的事件类型:'{Type}'{vbCrLf}请检查事件类型填写是否正确,或者 PCL 是否为最新版本。", "事件执行失败", ForceWait:=True)
  37. Exit Sub
  38. End If
  39. '调用事件处理器
  40. Try
  41. CustomEventProcessorsDict(Type)(Data)
  42. Catch ex As Exception
  43. Log(ex, $"自定义事件执行失败:{Type}", LogLevel.Msgbox, "自定义主页事件执行失败")
  44. End Try
  45. End Sub
  46. ''' <summary>
  47. ''' 存储各个自定义事件处理器的字典,以 EventType 为键,执行自定义事件时在工作线程中调用 Action 并传入分割后的 EventData 。
  48. ''' Action 应当在事件逻辑结束后,即可以开始下一个事件时再结束;以便于挨个调用事件不打架。
  49. ''' </summary>
  50. Private ReadOnly CustomEventProcessorsDict As New Dictionary(Of String, Action(Of String())) From {
  51. {"打开网页",
  52. Sub(Data As String())
  53. Data(0) = Data(0).Replace("\", "/")
  54. If Not Data(0).Contains("://") OrElse Data(0).StartsWithF("file", True) Then '为了支持更多协议(#2200)
  55. MyMsgBox("打开网页事件中,EventData 必须为一个网址。" & vbCrLf & "如果想要启动程序,请将 EventType 改为 打开文件。", "事件执行失败")
  56. Exit Sub
  57. End If
  58. Hint("网页正在开启中,请稍候……")
  59. OpenWebsite(Data(0))
  60. End Sub},
  61. {"打开文件",
  62. Sub(Data As String())
  63. Dim ActualPaths = GetEventAbsoluteUrls(Data(0), "打开文件")
  64. Dim Location = ActualPaths(0), WorkingDir = ActualPaths(1)
  65. Log($"[Control] 打开文件:路径:{Location},工作目录:{WorkingDir}")
  66. If Not CommandRunUserConfirm(If(Data.Length < 2, Location, $"{Location} {Data(1)}")) Then Exit Sub
  67. Dim Info As New ProcessStartInfo With {
  68. .Arguments = If(Data.Length < 2, "", Data(1)),
  69. .FileName = Location,
  70. .WorkingDirectory = ShortenPath(WorkingDir)
  71. }
  72. Process.Start(Info)
  73. End Sub},
  74. {"打开帮助",
  75. Sub(Data As String())
  76. Dim ActualPaths = GetEventAbsoluteUrls(Data(0), "打开帮助")
  77. Dim Location = ActualPaths(0)
  78. Log($"[Control] 打开帮助:{Location}")
  79. PageOtherHelp.EnterHelpPage(Location)
  80. End Sub},
  81. {"执行命令",
  82. Sub(Data As String())
  83. Dim ActualPaths = GetEventAbsoluteUrls(Data(0), "执行命令")
  84. Dim Location = ActualPaths(0), WorkingDir = ActualPaths(1)
  85. Log($"[Control] 执行命令:路径:{Location},工作目录:{WorkingDir}")
  86. If Not CommandRunUserConfirm(If(Data.Length < 2, Location, $"{Location} {Data(1)}")) Then Exit Sub
  87. Dim Info As New ProcessStartInfo With {
  88. .Arguments = If(Data.Length < 2, "", Data(1)),
  89. .FileName = Location,
  90. .WorkingDirectory = ShortenPath(WorkingDir)
  91. }
  92. Process.Start(Info)
  93. End Sub},
  94. {"启动游戏",
  95. Sub(Data As String())
  96. If Data(0) = "\current" Then
  97. If McVersionCurrent Is Nothing Then
  98. Hint("请先选择一个 Minecraft 版本!", HintType.Critical)
  99. Exit Sub
  100. Else
  101. Data(0) = McVersionCurrent.Name
  102. End If
  103. End If
  104. RunInUiWait(
  105. Sub()
  106. If McLaunchStart(New McLaunchOptions With {.Version = New McVersion(Data(0)), .ServerIp = If(Data.Length >= 2, Data(1), Nothing)}) Then
  107. Hint("正在启动 " & Data(0) & "……")
  108. End If
  109. End Sub)
  110. End Sub},
  111. {"复制文本", Sub(Data As String()) ClipboardSet(Join(Data, "|"))},
  112. {"刷新主页",
  113. Sub(Data As String())
  114. RunInUiWait(Sub() FrmLaunchRight.ForceRefresh())
  115. If Data(0) = "" Then Hint("已刷新主页!", HintType.Finish)
  116. End Sub},
  117. {"刷新帮助", Sub() RunInUi(Sub() PageOtherLeft.RefreshHelp())},
  118. {"今日人品", Sub() PageOtherTest.Jrrp()},
  119. {"内存优化",
  120. Sub(Data As String())
  121. If Data.Length >= 1 AndAlso Data(0) = "前台运行" Then
  122. PageOtherTest.MemoryOptimize(Data.Length >= 2 AndAlso Data(1) = "显示提示")
  123. Else
  124. RunInNewThread(Sub() PageOtherTest.MemoryOptimize(True))
  125. End If
  126. End Sub},
  127. {"弹出窗口", Sub(Data As String()) MyMsgBox(Data(1).Replace("\n", vbCrLf), Data(0).Replace("\n", vbCrLf), ForceWait:=True)},
  128. {"切换页面", Sub(Data As String()) RunInUiWait(Sub() FrmMain.PageChange(Val(Data(0)), Val(Data(1))))},
  129. {"安装整合包", Sub() RunInUiWait(Sub() ModpackInstall())},
  130. {"导入整合包", Sub() RunInUiWait(Sub() ModpackInstall())},
  131. {"下载文件",
  132. Sub(Data As String())
  133. Data(0) = Data(0).Replace("\", "/")
  134. If Not (Data(0).StartsWithF("http://", True) OrElse Data(0).StartsWithF("https://", True)) Then
  135. MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的下载协议。", "事件执行失败")
  136. Exit Sub
  137. End If
  138. Try
  139. Select Case Data.Length
  140. Case 1
  141. PageOtherTest.StartCustomDownload(Data(0), GetFileNameFromPath(Data(0)))
  142. Case 2
  143. PageOtherTest.StartCustomDownload(Data(0), Data(1))
  144. Case Else
  145. PageOtherTest.StartCustomDownload(Data(0), Data(1), Data(2))
  146. End Select
  147. Catch
  148. PageOtherTest.StartCustomDownload(Data(0), "未知")
  149. End Try
  150. End Sub}
  151. }
  152. #End Region
  153. #Region "工具方法"
  154. ''' <summary>
  155. ''' 弹窗询问用户是否同意继续执行打开类事件。
  156. ''' </summary>
  157. Private Function CommandRunUserConfirm(TextShowToUser As String) As Boolean
  158. If Setup.Get("HintCustomCommand") Then Return True
  159. Select Case MyMsgBox("即将执行:" & TextShowToUser & vbCrLf & "请在确认该操作没有安全隐患后继续。", "执行确认", "继续", "继续且今后不再要求确认", "取消")
  160. Case 1
  161. Return True
  162. Case 2
  163. Setup.Set("HintCustomCommand", True)
  164. Return True
  165. Case Else '3
  166. Log("[Control] 用户取消了打开类事件")
  167. Return False
  168. End Select
  169. End Function
  170. ''' <summary>
  171. ''' 返回自定义事件的绝对 Url。实际返回 {绝对 Url, WorkingDir}。
  172. ''' 失败会抛出异常。
  173. ''' </summary>
  174. Public Function GetEventAbsoluteUrls(RelativeUrl As String, EventType As String) As String()
  175. '网页确认
  176. If RelativeUrl.StartsWithF("http", True) Then
  177. If RunInUi() Then
  178. Throw New Exception("能打开联网帮助页面的 MyListItem 必须手动设置 Title、Info 属性!")
  179. End If
  180. '获取文件名
  181. Dim RawFileName As String
  182. Try
  183. RawFileName = GetFileNameFromPath(RelativeUrl)
  184. If Not RawFileName.EndsWithF(".json", True) Then Throw New Exception("未指向 .json 后缀的文件")
  185. Catch ex As Exception
  186. Throw New Exception("联网帮助页面须指向一个帮助 JSON 文件,并在同路径下包含相应 XAML 文件!" & vbCrLf &
  187. "例如:" & vbCrLf &
  188. " - https://www.baidu.com/test.json(填写这个路径)" & vbCrLf &
  189. " - https://www.baidu.com/test.xaml(同时也需要包含这个文件)", ex)
  190. End Try
  191. '下载文件
  192. Dim LocalTemp As String = RequestTaskTempFolder() & RawFileName
  193. Log("[Event] 转换网络资源:" & RelativeUrl & " -> " & LocalTemp)
  194. Try
  195. NetDownloadByClient(RelativeUrl, LocalTemp)
  196. NetDownloadByClient(RelativeUrl.Replace(".json", ".xaml"), LocalTemp.Replace(".json", ".xaml"))
  197. Catch ex As Exception
  198. Throw New Exception("下载指定的文件失败!" & vbCrLf &
  199. "注意,联网帮助页面须指向一个帮助 JSON 文件,并在同路径下包含相应 XAML 文件!" & vbCrLf &
  200. "例如:" & vbCrLf &
  201. " - https://www.baidu.com/test.json(填写这个路径)" & vbCrLf &
  202. " - https://www.baidu.com/test.xaml(同时也需要包含这个文件)", ex)
  203. End Try
  204. RelativeUrl = LocalTemp
  205. End If
  206. RelativeUrl = RelativeUrl.Replace("/", "\").ToLower.TrimStart("\")
  207. '确认实际路径
  208. Dim Location As String, WorkingDir As String = Path & "PCL"
  209. HelpTryExtract()
  210. If RelativeUrl.Contains(":\") Then
  211. '绝对路径
  212. Location = RelativeUrl
  213. Log("[Control] 自定义事件中由绝对路径" & EventType & ":" & Location)
  214. ElseIf File.Exists(Path & "PCL\" & RelativeUrl) Then
  215. '相对 PCL 文件夹的路径
  216. Location = Path & "PCL\" & RelativeUrl
  217. Log("[Control] 自定义事件中由相对 PCL 文件夹的路径" & EventType & ":" & Location)
  218. ElseIf File.Exists(Path & "PCL\Help\" & RelativeUrl) Then
  219. '相对 PCL 本地帮助文件夹的路径
  220. Location = Path & "PCL\Help\" & RelativeUrl
  221. WorkingDir = Path & "PCL\Help\"
  222. Log("[Control] 自定义事件中由相对 PCL 本地帮助文件夹的路径" & EventType & ":" & Location)
  223. ElseIf EventType = "打开帮助" AndAlso File.Exists(PathTemp & "Help\" & RelativeUrl) Then
  224. '相对 PCL 自带帮助文件夹的路径
  225. Location = PathTemp & "Help\" & RelativeUrl
  226. WorkingDir = PathTemp & "Help\"
  227. Log("[Control] 自定义事件中由相对 PCL 自带帮助文件夹的路径" & EventType & ":" & Location)
  228. ElseIf EventType = "打开文件" OrElse EventType = "执行命令" Then
  229. '直接使用原有路径启动程序
  230. Location = RelativeUrl
  231. Log("[Control] 自定义事件中直接" & EventType & ":" & Location)
  232. Else
  233. '打开帮助,但是格式不对劲
  234. Throw New FileNotFoundException("未找到 EventData 指向的本地 xaml 文件:" & RelativeUrl, RelativeUrl)
  235. End If
  236. Return {Location, WorkingDir}
  237. End Function
  238. #End Region
  239. End Module
  240. #Region "数据结构类声明"
  241. Public Class CustomEventCollection
  242. Inherits ObservableCollection(Of CustomEvent)
  243. Public Function DeepCloneToStructureList() As IList(Of CustomEventStructure)
  244. DeepCloneToStructureList = New List(Of CustomEventStructure)
  245. For Each AnEvent In Me
  246. DeepCloneToStructureList.Add(New CustomEventStructure With {.EventType = AnEvent.EventType, .EventData = AnEvent.EventData})
  247. Next
  248. End Function
  249. End Class
  250. Public Class CustomEvent
  251. Inherits DependencyObject
  252. Public Property EventType As String
  253. Get
  254. Return GetValue(EventTypeProperty)
  255. End Get
  256. Set(value As String)
  257. SetValue(EventTypeProperty, value)
  258. End Set
  259. End Property
  260. Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(CustomEvent), New PropertyMetadata(Nothing))
  261. Public Property EventData As String
  262. Get
  263. Return GetValue(EventDataProperty)
  264. End Get
  265. Set(value As String)
  266. SetValue(EventDataProperty, value)
  267. End Set
  268. End Property
  269. Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(CustomEvent), New PropertyMetadata(Nothing))
  270. End Class
  271. Public Structure CustomEventStructure
  272. Public EventType As String
  273. Public EventData As String
  274. End Structure
  275. #End Region