1 #Region "Microsoft.VisualBasic::91aa2f00c4507832c498edc591109700, 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 OverloadsSub 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 StringString)
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 StringImplements 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 StringAs 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 BooleanAs Integer
240             Return Start(WaitForExit)
241         End Function
242
243         Public Function Start(Optional waitForExit As Boolean = FalseAs 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