1 | #Region "Microsoft.VisualBasic::047864f664c5913e5c11d9c55477dcec, Microsoft.VisualBasic.Core\CommandLine\CLI\IORedirectFile.vb" |
2 | |
3 | ' Author: |
4 | ' |
5 | ' asuka (amethyst.asuka@gcmodeller.org) |
6 | ' xie (genetics@smrucc.org) |
7 | ' xieguigang (xie.guigang@live.com) |
8 | ' |
9 | ' Copyright (c) 2018 GPL3 Licensed |
10 | ' |
11 | ' |
12 | ' GNU GENERAL PUBLIC LICENSE (GPL3) |
13 | ' |
14 | ' |
15 | ' This program is free software: you can redistribute it and/or modify |
16 | ' it under the terms of the GNU General Public License as published by |
17 | ' the Free Software Foundation, either version 3 of the License, or |
18 | ' (at your option) any later version. |
19 | ' |
20 | ' This program is distributed in the hope that it will be useful, |
21 | ' but WITHOUT ANY WARRANTY; without even the implied warranty of |
22 | ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
23 | ' GNU General Public License for more details. |
24 | ' |
25 | ' You should have received a copy of the GNU General Public License |
26 | ' along with this program. If not, see <http://www.gnu.org/licenses/>. |
27 | |
28 | |
29 | |
30 | ' /********************************************************************************/ |
31 | |
32 | ' Summaries: |
33 | |
34 | ' Class IORedirectFile |
35 | ' |
36 | ' Properties: Bin, CLIArguments, StandardOutput |
37 | ' |
38 | ' Constructor: (+1 Overloads) Sub New |
39 | ' |
40 | ' Function: CopyRedirect, Run, (+2 Overloads) Start, ToString, writeScript |
41 | ' |
42 | ' Sub: __processExitHandle, (+2 Overloads) Dispose, Start |
43 | ' |
44 | ' |
45 | ' /********************************************************************************/ |
46 | |
47 | #End Region |
48 | |
49 | Imports System.Runtime.CompilerServices |
50 | Imports Microsoft.VisualBasic.ApplicationServices |
51 | Imports Microsoft.VisualBasic.Language |
52 | Imports Microsoft.VisualBasic.Parallel |
53 | Imports ValueTuple = System.Collections.Generic.KeyValuePair(Of String, String) |
54 | |
55 | Namespace CommandLine |
56 | |
57 | ''' <summary> |
58 | ''' Using this class object rather than <see cref="IORedirect"/> is more encouraged. |
59 | ''' (假若所建立的子进程并不需要进行终端交互,相较于<see cref="IORedirect"/>对象,更加推荐使用本对象类型来执行。 |
60 | ''' 似乎<see cref="IORedirect"/>对象在创建一个子进程的时候的对象IO重定向的句柄的处理有问题,所以在这里构建一个更加简单的类型对象, |
61 | ''' 这个IO重定向对象不具备终端交互功能) |
62 | ''' </summary> |
63 | ''' <remarks>先重定向到一个临时文件之中,然后再返回临时文件给用户代码</remarks> |
64 | Public Class IORedirectFile |
65 | Implements IDisposable, IIORedirectAbstract |
66 | |
67 | #Region "Temp File" |
68 | |
69 | ''' <summary> |
70 | ''' 重定向的临时文件 |
71 | ''' </summary> |
72 | ''' <remarks>当使用.tmp拓展名的时候会由于APP框架里面的GC线程里面的自动临时文件清理而产生冲突,所以这里需要其他的文件拓展名来避免这个冲突</remarks> |
73 | Protected ReadOnly _TempRedirect As String = App.GetAppSysTempFile(".proc_IO_std.out", App.PID) |
74 | |
75 | ''' <summary> |
76 | ''' shell文件接口 |
77 | ''' </summary> |
78 | Dim shellScript As String |
79 | #End Region |
80 | |
81 | ''' <summary> |
82 | ''' The target invoked process event has been exit with a specific return code.(目标派生子进程已经结束了运行并且返回了一个错误值) |
83 | ''' </summary> |
84 | ''' <param name="exitCode"></param> |
85 | ''' <param name="exitTime"></param> |
86 | ''' <remarks></remarks> |
87 | Public Event ProcessExit(exitCode As Integer, exitTime As String) Implements IIORedirectAbstract.ProcessExit |
88 | |
89 | ''' <summary> |
90 | ''' 目标子进程的终端标准输出 |
91 | ''' </summary> |
92 | ''' <returns></returns> |
93 | Public ReadOnly Property StandardOutput As String Implements IIORedirectAbstract.StandardOutput |
94 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
95 | Get |
96 | Return New IO.StreamReader(_TempRedirect).ReadToEnd |
97 | End Get |
98 | End Property |
99 | |
100 | Public ReadOnly Property Bin As String Implements IIORedirectAbstract.Bin |
101 | Public ReadOnly Property CLIArguments As String Implements IIORedirectAbstract.CLIArguments |
102 | |
103 | ''' <summary> |
104 | ''' 将目标子进程的标准终端输出文件复制到一个新的文本文件之中 |
105 | ''' </summary> |
106 | ''' <param name="CopyToPath"></param> |
107 | ''' <returns></returns> |
108 | Public Function CopyRedirect(CopyToPath As String) As Boolean |
109 | If CopyToPath.FileExists Then |
110 | Call FileIO.FileSystem.DeleteFile(CopyToPath) |
111 | End If |
112 | |
113 | Try |
114 | Call FileIO.FileSystem.CopyFile(_TempRedirect, CopyToPath) |
115 | Catch ex As Exception |
116 | Return False |
117 | End Try |
118 | |
119 | Return True |
120 | End Function |
121 | |
122 | ''' <summary> |
123 | ''' Using this class object rather than <see cref="IORedirect"/> is more |
124 | ''' encouraged if there is no console interactive with your folked |
125 | ''' process. |
126 | ''' </summary> |
127 | ''' <param name="file"> |
128 | ''' The program file. |
129 | ''' (请注意检查路径参数,假若路径之中包含有%这个符号的话,在调用cmd的时候会失败) |
130 | ''' </param> |
131 | ''' <param name="argv"> |
132 | ''' The program commandline arguments. |
133 | ''' (请注意检查路径参数,假若路径之中包含有%这个符号的话,在调用cmd的时候会失败) |
134 | ''' </param> |
135 | ''' <param name="environment">Temporary environment variable</param> |
136 | ''' <param name="FolkNew">Folk the process on a new console window if this parameter value is TRUE</param> |
137 | ''' <param name="stdRedirect">If not want to redirect the std out to your file, just leave this value blank.</param> |
138 | Sub New(file$, |
139 | Optional argv$ = "", |
140 | Optional environment As IEnumerable(Of ValueTuple) = Nothing, |
141 | Optional FolkNew As Boolean = False, |
142 | Optional stdRedirect$ = "", |
143 | Optional stdin$ = Nothing) |
144 | |
145 | If Not String.IsNullOrEmpty(stdRedirect) Then |
146 | _TempRedirect = stdRedirect.CLIPath |
147 | End If |
148 | |
149 | ' 没有小数点,说明可能只是一个命令,而不是具体的可执行程序文件名 |
150 | If InStr(file, ".") = 0 Then |
151 | ' do nothing |
152 | Else |
153 | ' 对于具体的程序文件的调用,在这里获取其完整路径 |
154 | Try |
155 | file = FileIO.FileSystem.GetFileInfo(file).FullName |
156 | Catch ex As Exception |
157 | ex = New Exception(file, ex) |
158 | Throw ex |
159 | End Try |
160 | End If |
161 | |
162 | Bin = file |
163 | argv = $"{argv.TrimNewLine(" ")} > {_TempRedirect}" |
164 | CLIArguments = argv |
165 | |
166 | ' 系统可能不会自动创建文件夹,则需要在这里使用这个方法来手工创建, |
167 | ' 避免出现无法找到文件的问题 |
168 | Call _TempRedirect.ParentPath.MkDIR |
169 | ' 在Unix平台上面这个文件不会被自动创建??? |
170 | Call "".SaveTo(_TempRedirect) |
171 | |
172 | If App.IsMicrosoftPlatform Then |
173 | shellScript = ScriptingExtensions.Cmd(file, argv, environment, FolkNew, stdin) |
174 | Else |
175 | shellScript = ScriptingExtensions.Bash(file, argv, environment, FolkNew, stdin) |
176 | End If |
177 | |
178 | Call $"""{file.ToFileURL}"" {argv}".__DEBUG_ECHO |
179 | End Sub |
180 | |
181 | ''' <summary> |
182 | ''' Start target child process and then wait for the child process exits. |
183 | ''' So that the thread will be stuck at here until the sub process is |
184 | ''' job done! |
185 | ''' (启动目标子进程,然后等待执行完毕并返回退出代码(请注意,在进程未执行完毕 |
186 | ''' 之前,整个线程会阻塞在这里)) |
187 | ''' </summary> |
188 | ''' <returns></returns> |
189 | Public Function Run() As Integer Implements IIORedirectAbstract.Run |
190 | Dim path$ = writeScript() |
191 | Dim exitCode As Integer |
192 | |
193 | #If UNIX Then |
194 | ' xdg-open: file '/tmp/gut_16s/15201/tmp00003.sh' does not exist |
195 | With New Process() With { |
196 | .StartInfo = New ProcessStartInfo(path) |
197 | } |
198 | Call .Start() |
199 | Call .WaitForExit() |
200 | |
201 | exitCode = .ExitCode |
202 | End With |
203 | #Else |
204 | ' [ERROR 12/10/2017 4:57:27 AM] <Print>::System.Exception: Print ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.DllNotFoundException: kernel32 |
205 | ' at (wrapper managed-to-native) Microsoft.VisualBasic.CompilerServices.NativeMethods:GetStartupInfo (Microsoft.VisualBasic.CompilerServices.NativeTypes/STARTUPINFO) |
206 | ' at Microsoft.VisualBasic.Interaction.Shell (System.String PathName, Microsoft.VisualBasic.AppWinStyle Style, System.Boolean Wait, System.Int32 Timeout) [0x00077] in <828807dda9f14f24a7db780c6c644162>:0 |
207 | ' at Microsoft.VisualBasic.CommandLine.IORedirectFile.Run () [0x00011] in <d9cf6734998c48a092e8a1528ac0142f>:0 |
208 | ' at SMRUCC.genomics.Analysis.Metagenome.Mothur.RunMothur (System.String args) [0x00014] in <d8095d5f77564ae4af334ce9b17144fb>:0 |
209 | ' at SMRUCC.genomics.Analysis.Metagenome.Mothur.Make_contigs (System.String file, System.Int32 processors) [0x00013] in <d8095d5f77564ae4af334ce9b17144fb>:0 |
210 | ' at SMRUCC.genomics.Analysis.Metagenome.MothurContigsOTU.ClusterOTUByMothur (System.String left, System.String right, System.String silva, System.String workspace, System.Int32 processor) [0x00060] in <d8095d5f77564ae4af334ce9b17144fb>:0 |
211 | ' at meta_community.CLI.ClusterOTU (Microsoft.VisualBasic.CommandLine.CommandLine args) [0x0004b] in <58a80ce28e644b22a21c332e0f3bd1f5>:0 |
212 | ' at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&) |
213 | ' at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in <902ab9e386384bec9c07fa19aa938869>:0 |
214 | ' --- End of inner exception stack trace --- |
215 | ' at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00048] in <902ab9e386384bec9c07fa19aa938869>:0 |
216 | ' at System.Reflection.MethodBase.Invoke (System.Object obj, System.Object[] parameters) [0x00000] in <902ab9e386384bec9c07fa19aa938869>:0 |
217 | ' at Microsoft.VisualBasic.CommandLine.Reflection.EntryPoints.APIEntryPoint.__directInvoke (System.Object[] callParameters, System.Object target, System.Boolean Throw) [0x0000c] in <d9cf6734998c48a092e8a1528ac0142f>:0 |
218 | ' --- End of inner exception stack trace --- |
219 | |
220 | exitCode = Interaction.Shell( |
221 | path, |
222 | Style:=AppWinStyle.Hide, |
223 | Wait:=True |
224 | ) |
225 | #End If |
226 | Call path.Delete |
227 | |
228 | Return exitCode |
229 | End Function |
230 | |
231 | Private Function writeScript() As String |
232 | Dim ext$ = If(App.IsMicrosoftPlatform, ".bat", ".sh") |
233 | Dim path$ = App.GetAppSysTempFile(ext, App.PID) |
234 | Call shellScript.SaveTo(path) |
235 | Return path |
236 | End Function |
237 | |
238 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
239 | Public Function Start(WaitForExit As Boolean, PushingData As String(), _DISP_DEBUG_INFO As Boolean) As Integer |
240 | Return Start(WaitForExit) |
241 | End Function |
242 | |
243 | Public Function Start(Optional waitForExit As Boolean = False) As Integer Implements IIORedirectAbstract.Start |
244 | If waitForExit Then |
245 | Return Run() |
246 | Else |
247 | Call Start(procExitCallback:=Nothing) |
248 | End If |
249 | |
250 | Return 0 |
251 | End Function |
252 | |
253 | ''' <summary> |
254 | ''' 启动子进程,但是不等待执行完毕,当目标子进程退出的时候,回调<paramref name="procExitCallback"/>函数句柄 |
255 | ''' </summary> |
256 | ''' <param name="procExitCallback"></param> |
257 | ''' |
258 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
259 | Public Sub Start(Optional procExitCallback As Action = Nothing) |
260 | Call New Tasks.Task(Of Action)(procExitCallback, AddressOf __processExitHandle).Start() |
261 | End Sub |
262 | |
263 | Private Sub __processExitHandle(ProcessExitCallback As Action) |
264 | Dim ExitCode = Run() |
265 | |
266 | RaiseEvent ProcessExit(ExitCode, Now.ToString) |
267 | |
268 | If Not ProcessExitCallback Is Nothing Then |
269 | Call ProcessExitCallback() |
270 | End If |
271 | End Sub |
272 | |
273 | Public Overrides Function ToString() As String |
274 | Return shellScript |
275 | End Function |
276 | |
277 | #Region "IDisposable Support" |
278 | Private disposedValue As Boolean ' To detect redundant calls |
279 | |
280 | ' IDisposable |
281 | Protected Overridable Sub Dispose(disposing As Boolean) |
282 | If Not Me.disposedValue Then |
283 | If disposing Then |
284 | ' TODO: dispose managed state (managed objects). |
285 | ' 清理临时文件 |
286 | On Error Resume Next |
287 | |
288 | Call FileIO.FileSystem.DeleteFile(Me._TempRedirect, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.DeletePermanently) |
289 | End If |
290 | |
291 | ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below. |
292 | ' TODO: set large fields to null. |
293 | End If |
294 | Me.disposedValue = True |
295 | End Sub |
296 | |
297 | ' TODO: override Finalize() only if Dispose(disposing As Boolean) above has code to free unmanaged resources. |
298 | 'Protected Overrides Sub Finalize() |
299 | ' ' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above. |
300 | ' Dispose(False) |
301 | ' MyBase.Finalize() |
302 | 'End Sub |
303 | |
304 | ' This code added by Visual Basic to correctly implement the disposable pattern. |
305 | Public Sub Dispose() Implements IDisposable.Dispose |
306 | ' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above. |
307 | Dispose(True) |
308 | ' TODO: uncomment the following line if Finalize() is overridden above. |
309 | ' GC.SuppressFinalize(Me) |
310 | End Sub |
311 | #End Region |
312 | End Class |
313 | End Namespace |