1 #Region "Microsoft.VisualBasic::c27480142aa2c1fe1e6a99cbce1d2a10, Microsoft.VisualBasic.Core\CommandLine\CLI\IORedirect.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 IORedirect
35     
36     '         Properties: Bin, CLIArguments, ProcessInfo, StandardOutput
37     '         Delegate Function
38     
39     
40     '         Delegate Sub
41     
42     '             Constructor: (+1 OverloadsSub New
43     
44     '             Function: __tryGetSTDOUT, GetError, Read, ReadLine, Run
45     '                       Shell, (+3 Overloads) Start, ToString
46     
47     '             Sub: __detectProcessExit, __listenSTDOUT, (+2 Overloads) Dispose, (+2 Overloads) WriteLine
48     
49     
50     
51     
52     
53     ' /********************************************************************************/
54
55 #End Region
56
57 Imports System.Text.RegularExpressions
58 Imports System.Text
59 Imports System.Runtime.CompilerServices
60 Imports System.Reflection
61 Imports System.Threading
62 Imports Microsoft.VisualBasic.Terminal.STDIO
63 Imports Microsoft.VisualBasic.Terminal.STDIO__
64 Imports Microsoft.VisualBasic.Parallel
65
66 Namespace CommandLine
67
68     ''' <summary>
69     ''' A communication fundation class type for the commandline program interop.
70     ''' (一个简单的用于从当前进程派生子进程的Wrapper对象,假若需要folk出来的子进程对象
71     ''' 不需要终端交互功能,则更加推荐使用<see cref="IORedirectFile"/>对象来进行调用)
72     ''' </summary>
73     ''' <remarks></remarks>
74     Public Class IORedirect : Implements I_ConsoleDeviceHandle
75         Implements IDisposable, IIORedirectAbstract
76
77         ''' <summary>
78         ''' 当前的这个进程实例是否处于运行的状态
79         ''' </summary>
80         ''' <remarks></remarks>
81         Dim _processStateRunning As Boolean
82         Dim _consoleDevice As IO.StreamReader
83         Dim _inputDevice As IO.StreamWriter
84         Dim _errorLogsDevice As IO.StreamReader
85         Dim _DispInvokedSTDOUT As Boolean
86
87         ''' <summary>
88         ''' The target invoked process event has been exit with a specific return code.
89         ''' (目标派生子进程已经结束了运行并且返回了一个错误值)
90         ''' </summary>
91         ''' <param name="exitCode"></param>
92         ''' <param name="exitTime"></param>
93         ''' <remarks></remarks>
94         Public Event ProcessExit(exitCode As Integer, exitTime As StringImplements IIORedirectAbstract.ProcessExit
95         Public Event DataArrival(s As String)
96
97         ''' <summary>
98         ''' The process invoke interface of current IO redirect operation.
99         ''' </summary>
100         ''' <value></value>
101         ''' <returns></returns>
102         ''' <remarks></remarks>
103         Public ReadOnly Property ProcessInfo As Process
104
105         ''' <summary>
106         ''' Gets the standard output for the target invoke process.
107         ''' </summary>
108         ''' <value></value>
109         ''' <returns></returns>
110         ''' <remarks></remarks>
111         Public ReadOnly Property StandardOutput As String Implements IIORedirectAbstract.StandardOutput
112             Get
113                 Return __STDOUT_BUFFER.ToString
114             End Get
115         End Property
116
117         Public ReadOnly Property Bin As String Implements IIORedirectAbstract.Bin
118             Get
119                 Return ProcessInfo.StartInfo.FileName
120             End Get
121         End Property
122         Public ReadOnly Property CLIArguments As String Implements IIORedirectAbstract.CLIArguments
123             Get
124                 Return ProcessInfo.StartInfo.Arguments
125             End Get
126         End Property
127
128         Dim __STDOUT_BUFFER As StringBuilder = New StringBuilder(1024)
129
130         Public Delegate Function ProcessAyHandle(WaitForExit As Boolean, PushingData As String(), _DISP_DEBUG_INFO As BooleanAs Integer
131
132         ''' <summary>
133         ''' A function pointer for process the events when the target invoked child process was terminated and exit.
134         ''' (当目标进程退出的时候所调用的过程)
135         ''' </summary>
136         ''' <param name="exitCode">The exit code for the target sub invoke process.进程的退出代码</param>
137         ''' <param name="exitTime">The exit time for the target sub invoke process.(进程的退出时间)</param>
138         ''' <remarks></remarks>
139         Public Delegate Sub ProcessExitCallBack(exitCode As Integer, exitTime As String)
140
141         ''' <summary>
142         ''' Gets a <see cref="String"/> used to read the error output of the application.
143         ''' </summary>
144         ''' <returns>A <see cref="String"/> text value that read from the std_error of <see cref="System.IO.StreamReader"/> 
145         ''' that can be used to read the standard error stream of the application.</returns>
146         Public Function GetError() As String
147             Return _errorLogsDevice.ReadToEnd
148         End Function
149
150         ''' <summary>
151         ''' Start the target process. If the target invoked process is currently on the running state, 
152         ''' then this function will returns the -100 value as error code and print the warning 
153         ''' information on the system console.(启动目标进程)
154         ''' </summary>
155         ''' <param name="WaitForExit">
156         ''' Indicate that the program code wait for the target process exit or not?
157         ''' (参数指示应用程序代码是否等待目标进程的结束)
158         ''' </param>
159         ''' <returns>
160         ''' 当发生错误的时候会返回错误代码,当当前的进程任然处于运行的状态的时候,程序会返回-100错误代码并在终端之上打印出警告信息
161         ''' </returns>
162         ''' <remarks></remarks>
163         Public Function Start(Optional WaitForExit As Boolean = False,
164                               Optional PushingData As String() = Nothing,
165                               Optional _DISP_DEBUG_INFO As Boolean = FalseAs Integer
166
167             If _processStateRunning Then
168                 Dim msg As String = $"Target process ""{ProcessInfo.StartInfo.FileName.ToFileURL}"" is currently in the running state, Operation abort!"
169                 Call VBDebugger.Warning(msg)
170                 Return -100
171             End If
172
173             If _DISP_DEBUG_INFO Then
174                 Dim Exe As String = FileIO.FileSystem.GetFileInfo(ProcessInfo.StartInfo.FileName).FullName.Replace("\""/")
175                 Dim argvs As String = If(String.IsNullOrEmpty(ProcessInfo.StartInfo.Arguments), """ " & ProcessInfo.StartInfo.Arguments)
176                 Call Console.WriteLine("      ---> system(""file:''{0}""{1})", Exe, argvs)
177             End If
178
179             Try
180                 Call ProcessInfo.Start()
181             Catch ex As Exception
182                 Call Printf("FATAL_ERROR::%s", ex.ToString)
183                 Call Console.WriteLine("  Exe ==> " & ProcessInfo.StartInfo.FileName)
184                 Call Console.WriteLine("argvs ==> " & ProcessInfo.StartInfo.Arguments)
185
186                 Return ex.HResul '4.5
187                 Return -1
188             End Try
189
190             _processStateRunning = True
191             If _IORedirect Then
192                 _consoleDevice = ProcessInfo.StandardOutput
193                 _errorLogsDevice = ProcessInfo.StandardError
194             End If
195
196             _inputDevice = ProcessInfo.StandardInput
197
198             Dim ListeningThreadA As System.Action = AddressOf Me.__listenSTDOUT
199             Dim DetectExitThread As System.Action = AddressOf Me.__detectProcessExit
200
201             If _IORedirect Then
202                 Call ListeningThreadA.BeginInvoke(Nothing, Nothing)
203             End If
204             Call DetectExitThread.BeginInvoke(Nothing, Nothing)
205
206             If Not PushingData.IsNullOrEmpty Then
207
208                 For Each Line As String In PushingData
209                     Call _inputDevice.WriteLine(Line)
210                     Call _inputDevice.Flush()
211                     Call Console.WriteLine("  >>>   " & Line)
212                 Next
213
214             End If
215
216             If WaitForExit Then
217                 Call ProcessInfo.WaitForExit()
218                 Call Thread.Sleep(100)
219                 Call OperationTimeOut(AddressOf __tryGetSTDOUT, 2)  '将剩余的标准输出之中的数据完全的打印出来
220
221                 Return ProcessInfo.ExitCode
222             Else
223                 Call Parallel.RunTask(AddressOf ProcessInfo.WaitForExit)
224                 Return 0
225             End If
226         End Function
227
228         ''' <summary>
229         ''' Start the target process.(启动目标进程)
230         ''' </summary>
231         ''' <returns>当发生错误的时候会返回错误代码</returns>
232         ''' <remarks></remarks>
233         Public Function Start(ProcessExitCallBack As ProcessExitCallBack,
234                               Optional PushingData As String() = Nothing,
235                               Optional _DISP_DEBUG_INFO As Boolean = FalseAs Integer
236             AddHandler Me.ProcessExit, Sub(exitCode As Integer, exitTime As StringCall ProcessExitCallBack(exitCode, exitTime)
237             Return Start(WaitForExit:=False,
238                          PushingData:=PushingData,
239                          _DISP_DEBUG_INFO:=_DISP_DEBUG_INFO)
240         End Function
241
242         Public Sub WriteLine(s As StringImplements I_ConsoleDeviceHandle.WriteLine
243             Call _inputDevice.WriteLine(s)
244             Call _inputDevice.Flush()
245         End Sub
246
247         ''' <summary>
248         ''' 输出目标子进程的标准输出设备的内容
249         ''' </summary>
250         ''' <remarks></remarks>
251         Private Sub __listenSTDOUT()
252             Call $"{MethodBase.GetCurrentMethod.ToString} threading start!  ProcessStateExit = {_processStateRunning.ToString}".__DEBUG_ECHO
253
254             Do While _processStateRunning = True
255                 Call __tryGetSTDOUT()
256                 Call Thread.Sleep(10)
257             Loop
258
259             Call $"{MethodBase.GetCurrentMethod.ToString}(""{Me.ProcessInfo.StartInfo.FileName.ToFileURL}"") threading exit!".__DEBUG_ECHO
260         End Sub
261
262         Private Function __tryGetSTDOUT() As String
263             Dim readBuffer As String = _consoleDevice.ReadToEnd   '进程退出去了之后会卡在这里???
264
265             If Len(readBuffer) = 0 Then
266                 Call Thread.Sleep(10)
267                 Return ""
268             End If
269
270             Call __STDOUT_BUFFER.Append(readBuffer)
271             If _DispInvokedSTDOUT Then Call Console.WriteLine(readBuffer)
272
273             RaiseEvent DataArrival(readBuffer)
274
275             Return readBuffer
276         End Function
277
278         ''' <summary>
279         ''' 检测目标子进程是否已经结束
280         ''' </summary>
281         ''' <remarks></remarks>
282         Private Sub __detectProcessExit()
283             Call $"{MethodBase.GetCurrentMethod.ToString} threading start!".__DEBUG_ECHO
284
285             Do While Not ProcessInfo.HasExited
286                 Call Thread.Sleep(10)
287             Loop
288
289             Call "Invoked process has been exit...".__DEBUG_ECHO
290             _processStateRunning = False
291             RaiseEvent ProcessExit(ProcessInfo.ExitCode, ProcessInfo.ExitTime.ToString)
292         End Sub
293
294         Public Overrides Function ToString() As String
295             Return String.Format("{0} {1}", ProcessInfo.StartInfo.FileName, ProcessInfo.StartInfo.Arguments)
296         End Function
297
298         ''' <summary>
299         ''' 在进行隐士转换的时候,假若可执行文件的文件路径之中含有空格,则这个时候应该要特别的小心
300         ''' </summary>
301         ''' <param name="CLI"></param>
302         ''' <returns></returns>
303         Public Shared Widening Operator CType(CLI As StringAs IORedirect
304 #If DEBUG Then
305             Call CLI.__DEBUG_ECHO
306 #End If
307             Dim Tokens As String() = Regex.Split(CLI, SPLIT_REGX_EXPRESSION)
308             Dim EXE As String = Tokens.First
309             Dim args As String = Mid$(CLI, Len(EXE) + 1)
310
311             Return New IORedirect(EXE, args)
312         End Operator
313
314         ReadOnly _IORedirect As Boolean
315
316         ''' <summary>
317         ''' Creates a <see cref="System.Diagnostics.Process"/> wrapper for the CLI program operations.
318         ''' (在服务器上面可能会有一些线程方面的兼容性BUG的问题,不太清楚为什么会导致这样)
319         ''' </summary>
320         ''' <param name="Exe">The file path of the executable file.</param>
321         ''' <param name="args">The CLI arguments for the folked program.</param>
322         ''' <param name="envir">Set up the environment variable for the target invoked child process.</param>
323         ''' <param name="_disp_debug"></param>
324         ''' <param name="disp_STDOUT">是否显示目标被调用的外部程序的标准输出</param>
325         ''' <remarks></remarks>
326         Public Sub New(Exe As String,
327                        Optional args As String = "",
328                        Optional envir As IEnumerable(Of KeyValuePair(Of StringString)) = Nothing,
329                        Optional IORedirect As Boolean = True,
330                        Optional disp_STDOUT As Boolean = True,
331                        Optional _disp_debug As Boolean = False)
332
333             Dim pInfo As New ProcessStartInfo(Exe.GetString(""""c), args)
334
335             _IORedirect = IORedirect
336             pInfo.UseShellExecute = False
337             If IORedirect Then  '只是重定向输出设备流
338                 pInfo.RedirectStandardOutput = True
339                 pInfo.RedirectStandardError = True
340             End If
341             pInfo.RedirectStandardInput = True
342             pInfo.ErrorDialog = False
343             pInfo.WindowStyle = ProcessWindowStyle.Hidden
344             pInfo.CreateNoWindow = True
345
346             If Not envir Is Nothing Then
347                 For Each para As KeyValuePair(Of StringStringIn envir
348                     Call pInfo.EnvironmentVariables.Add(para.Key, para.Value)
349                 Next
350             End If
351
352             _DispInvokedSTDOUT = disp_STDOUT
353             _ProcessInfo = New Process
354             _ProcessInfo.EnableRaisingEvents = True
355             _ProcessInfo.StartInfo = pInfo
356             _processStateRunning = False
357
358             If _disp_debug Then Call $"""{Exe}"" {args}".__DEBUG_ECHO
359             If _disp_debug Then Call Console.WriteLine(If(disp_STDOUT, "disp_STDOUT  -> Yes!""disp_STDOUT   -> NO!"))
360         End Sub
361
362         Public Shared Function Shell(CommandLine As StringAs IORedirect
363             Return CType(CommandLine, IORedirect)
364         End Function
365
366         Public Function Read() As Integer Implements I_ConsoleDeviceHandle.Read
367             Return _consoleDevice.Read
368         End Function
369
370         Public Function ReadLine() As String Implements I_ConsoleDeviceHandle.ReadLine
371             Return _consoleDevice.ReadLine
372         End Function
373
374         Public Sub WriteLine(s As StringParamArray args() As StringImplements I_ConsoleDeviceHandle.WriteLine
375             Call Me._inputDevice.WriteLine(String.Format(s, args))
376             Call Me._inputDevice.Flush()
377         End Sub
378
379 #Region "IDisposable Support"
380         Private disposedValue As Boolean To detect redundant calls
381
382         ' IDisposable
383         Protected Overridable Sub Dispose(disposing As Boolean)
384             If Not Me.disposedValue Then
385                 If disposing Then
386                     ' TODO: dispose managed state (managed objects).
387                     Call ProcessInfo.Close()
388                     Call ProcessInfo.Dispose()
389                 End If
390
391                 ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
392                 ' TODO: set large fields to null.
393             End If
394             Me.disposedValue = True
395         End Sub
396
397         ' TODO: override Finalize() only if Dispose(      disposing As Boolean) above has code to free unmanaged resources.
398         'Protected Overrides Sub Finalize()
399         '    ' Do not change this code.  Put cleanup code in Dispose(      disposing As Boolean) above.
400         '    Dispose(False)
401         '    MyBase.Finalize()
402         'End Sub
403
404         ' This code added by Visual Basic to correctly implement the disposable pattern.
405         Public Sub Dispose() Implements IDisposable.Dispose
406             Do not change this code.  Put cleanup code in Dispose(disposing As Boolean) above.
407             Dispose(True)
408             GC.SuppressFinalize(Me)
409         End Sub
410 #End Region
411
412         ''' <summary>
413         ''' Gets the value that the associated process specified when it terminated.
414         ''' </summary>
415         ''' <param name="WaitForExit"></param>
416         ''' <returns>The code that the associated process specified when it terminated.</returns>
417         Public Function Start(Optional WaitForExit As Boolean = FalseAs Integer Implements IIORedirectAbstract.Start
418             Return Start(WaitForExit, NothingTrue)
419         End Function
420
421         ''' <summary>
422         ''' 线程会被阻塞在这里,直到外部应用程序执行完毕
423         ''' </summary>
424         ''' <returns></returns>
425         Public Function Run() As Integer Implements IIORedirectAbstract.Run
426             Return Start(WaitForExit:=True)
427         End Function
428     End Class
429 End Namespace