430 lines
18 KiB
VB.net
430 lines
18 KiB
VB.net
#Region "Microsoft.VisualBasic::d082aedd7c045ae5a26d30b664903b85, Microsoft.VisualBasic.Core\CommandLine\CLI\IORedirect.vb"
|
||
|
||
' Author:
|
||
'
|
||
' asuka (amethyst.asuka@gcmodeller.org)
|
||
' xie (genetics@smrucc.org)
|
||
' xieguigang (xie.guigang@live.com)
|
||
'
|
||
' Copyright (c) 2018 GPL3 Licensed
|
||
'
|
||
'
|
||
' GNU GENERAL PUBLIC LICENSE (GPL3)
|
||
'
|
||
'
|
||
' This program is free software: you can redistribute it and/or modify
|
||
' it under the terms of the GNU General Public License as published by
|
||
' the Free Software Foundation, either version 3 of the License, or
|
||
' (at your option) any later version.
|
||
'
|
||
' This program is distributed in the hope that it will be useful,
|
||
' but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
' GNU General Public License for more details.
|
||
'
|
||
' You should have received a copy of the GNU General Public License
|
||
' along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
|
||
|
||
' /********************************************************************************/
|
||
|
||
' Summaries:
|
||
|
||
' Class IORedirect
|
||
'
|
||
' Properties: Bin, CLIArguments, ProcessInfo, StandardOutput
|
||
' Delegate Function
|
||
'
|
||
'
|
||
' Delegate Sub
|
||
'
|
||
' Constructor: (+1 Overloads) Sub New
|
||
'
|
||
' Function: __tryGetSTDOUT, GetError, Read, ReadLine, Run
|
||
' Shell, (+3 Overloads) Start, ToString
|
||
'
|
||
' Sub: __detectProcessExit, __listenSTDOUT, (+2 Overloads) Dispose, (+2 Overloads) WriteLine
|
||
'
|
||
'
|
||
'
|
||
'
|
||
'
|
||
' /********************************************************************************/
|
||
|
||
#End Region
|
||
|
||
Imports System.Text.RegularExpressions
|
||
Imports System.Text
|
||
Imports System.Runtime.CompilerServices
|
||
Imports System.Reflection
|
||
Imports System.Threading
|
||
Imports Microsoft.VisualBasic.Terminal.STDIO
|
||
Imports Microsoft.VisualBasic.Terminal.STDIO__
|
||
Imports Microsoft.VisualBasic.Parallel
|
||
|
||
Namespace CommandLine
|
||
|
||
''' <summary>
|
||
''' A communication fundation class type for the commandline program interop.
|
||
''' (一个简单的用于从当前进程派生子进程的Wrapper对象,假若需要folk出来的子进程对象
|
||
''' 不需要终端交互功能,则更加推荐使用<see cref="IORedirectFile"/>对象来进行调用)
|
||
''' </summary>
|
||
''' <remarks></remarks>
|
||
Public Class IORedirect : Implements I_ConsoleDeviceHandle
|
||
Implements IDisposable, IIORedirectAbstract
|
||
|
||
''' <summary>
|
||
''' 当前的这个进程实例是否处于运行的状态
|
||
''' </summary>
|
||
''' <remarks></remarks>
|
||
Dim _processStateRunning As Boolean
|
||
Dim _consoleDevice As IO.StreamReader
|
||
Dim _inputDevice As IO.StreamWriter
|
||
Dim _errorLogsDevice As IO.StreamReader
|
||
Dim _DispInvokedSTDOUT As Boolean
|
||
|
||
''' <summary>
|
||
''' The target invoked process event has been exit with a specific return code.
|
||
''' (目标派生子进程已经结束了运行并且返回了一个错误值)
|
||
''' </summary>
|
||
''' <param name="exitCode"></param>
|
||
''' <param name="exitTime"></param>
|
||
''' <remarks></remarks>
|
||
Public Event ProcessExit(exitCode As Integer, exitTime As String) Implements IIORedirectAbstract.ProcessExit
|
||
Public Event DataArrival(s As String)
|
||
|
||
''' <summary>
|
||
''' The process invoke interface of current IO redirect operation.
|
||
''' </summary>
|
||
''' <value></value>
|
||
''' <returns></returns>
|
||
''' <remarks></remarks>
|
||
Public ReadOnly Property ProcessInfo As Process
|
||
|
||
''' <summary>
|
||
''' Gets the standard output for the target invoke process.
|
||
''' </summary>
|
||
''' <value></value>
|
||
''' <returns></returns>
|
||
''' <remarks></remarks>
|
||
Public ReadOnly Property StandardOutput As String Implements IIORedirectAbstract.StandardOutput
|
||
Get
|
||
Return __STDOUT_BUFFER.ToString
|
||
End Get
|
||
End Property
|
||
|
||
Public ReadOnly Property Bin As String Implements IIORedirectAbstract.Bin
|
||
Get
|
||
Return ProcessInfo.StartInfo.FileName
|
||
End Get
|
||
End Property
|
||
Public ReadOnly Property CLIArguments As String Implements IIORedirectAbstract.CLIArguments
|
||
Get
|
||
Return ProcessInfo.StartInfo.Arguments
|
||
End Get
|
||
End Property
|
||
|
||
Dim __STDOUT_BUFFER As StringBuilder = New StringBuilder(1024)
|
||
|
||
Public Delegate Function ProcessAyHandle(WaitForExit As Boolean, PushingData As String(), _DISP_DEBUG_INFO As Boolean) As Integer
|
||
|
||
''' <summary>
|
||
''' A function pointer for process the events when the target invoked child process was terminated and exit.
|
||
''' (当目标进程退出的时候所调用的过程)
|
||
''' </summary>
|
||
''' <param name="exitCode">The exit code for the target sub invoke process.进程的退出代码</param>
|
||
''' <param name="exitTime">The exit time for the target sub invoke process.(进程的退出时间)</param>
|
||
''' <remarks></remarks>
|
||
Public Delegate Sub ProcessExitCallBack(exitCode As Integer, exitTime As String)
|
||
|
||
''' <summary>
|
||
''' Gets a <see cref="String"/> used to read the error output of the application.
|
||
''' </summary>
|
||
''' <returns>A <see cref="String"/> text value that read from the std_error of <see cref="System.IO.StreamReader"/>
|
||
''' that can be used to read the standard error stream of the application.</returns>
|
||
Public Function GetError() As String
|
||
Return _errorLogsDevice.ReadToEnd
|
||
End Function
|
||
|
||
''' <summary>
|
||
''' Start the target process. If the target invoked process is currently on the running state,
|
||
''' then this function will returns the -100 value as error code and print the warning
|
||
''' information on the system console.(启动目标进程)
|
||
''' </summary>
|
||
''' <param name="WaitForExit">
|
||
''' Indicate that the program code wait for the target process exit or not?
|
||
''' (参数指示应用程序代码是否等待目标进程的结束)
|
||
''' </param>
|
||
''' <returns>
|
||
''' 当发生错误的时候会返回错误代码,当当前的进程任然处于运行的状态的时候,程序会返回-100错误代码并在终端之上打印出警告信息
|
||
''' </returns>
|
||
''' <remarks></remarks>
|
||
Public Function Start(Optional WaitForExit As Boolean = False,
|
||
Optional PushingData As String() = Nothing,
|
||
Optional _DISP_DEBUG_INFO As Boolean = False) As Integer
|
||
|
||
If _processStateRunning Then
|
||
Dim msg As String = $"Target process ""{ProcessInfo.StartInfo.FileName.ToFileURL}"" is currently in the running state, Operation abort!"
|
||
Call VBDebugger.Warning(msg)
|
||
Return -100
|
||
End If
|
||
|
||
If _DISP_DEBUG_INFO Then
|
||
Dim Exe As String = FileIO.FileSystem.GetFileInfo(ProcessInfo.StartInfo.FileName).FullName.Replace("\", "/")
|
||
Dim argvs As String = If(String.IsNullOrEmpty(ProcessInfo.StartInfo.Arguments), "", " " & ProcessInfo.StartInfo.Arguments)
|
||
Call Console.WriteLine(" ---> system(""file:''{0}""{1})", Exe, argvs)
|
||
End If
|
||
|
||
Try
|
||
Call ProcessInfo.Start()
|
||
Catch ex As Exception
|
||
Call Printf("FATAL_ERROR::%s", ex.ToString)
|
||
Call Console.WriteLine(" Exe ==> " & ProcessInfo.StartInfo.FileName)
|
||
Call Console.WriteLine("argvs ==> " & ProcessInfo.StartInfo.Arguments)
|
||
|
||
' Return ex.HResul '4.5
|
||
Return -1
|
||
End Try
|
||
|
||
_processStateRunning = True
|
||
If _IORedirect Then
|
||
_consoleDevice = ProcessInfo.StandardOutput
|
||
_errorLogsDevice = ProcessInfo.StandardError
|
||
End If
|
||
|
||
_inputDevice = ProcessInfo.StandardInput
|
||
|
||
Dim ListeningThreadA As System.Action = AddressOf Me.__listenSTDOUT
|
||
Dim DetectExitThread As System.Action = AddressOf Me.__detectProcessExit
|
||
|
||
If _IORedirect Then
|
||
Call ListeningThreadA.BeginInvoke(Nothing, Nothing)
|
||
End If
|
||
Call DetectExitThread.BeginInvoke(Nothing, Nothing)
|
||
|
||
If Not PushingData.IsNullOrEmpty Then
|
||
|
||
For Each Line As String In PushingData
|
||
Call _inputDevice.WriteLine(Line)
|
||
Call _inputDevice.Flush()
|
||
Call Console.WriteLine(" >>> " & Line)
|
||
Next
|
||
|
||
End If
|
||
|
||
If WaitForExit Then
|
||
Call ProcessInfo.WaitForExit()
|
||
Call Thread.Sleep(100)
|
||
Call OperationTimeOut(AddressOf __tryGetSTDOUT, 2) '将剩余的标准输出之中的数据完全的打印出来
|
||
|
||
Return ProcessInfo.ExitCode
|
||
Else
|
||
Call Parallel.RunTask(AddressOf ProcessInfo.WaitForExit)
|
||
Return 0
|
||
End If
|
||
End Function
|
||
|
||
''' <summary>
|
||
''' Start the target process.(启动目标进程)
|
||
''' </summary>
|
||
''' <returns>当发生错误的时候会返回错误代码</returns>
|
||
''' <remarks></remarks>
|
||
Public Function Start(ProcessExitCallBack As ProcessExitCallBack,
|
||
Optional PushingData As String() = Nothing,
|
||
Optional _DISP_DEBUG_INFO As Boolean = False) As Integer
|
||
AddHandler Me.ProcessExit, Sub(exitCode As Integer, exitTime As String) Call ProcessExitCallBack(exitCode, exitTime)
|
||
Return Start(WaitForExit:=False,
|
||
PushingData:=PushingData,
|
||
_DISP_DEBUG_INFO:=_DISP_DEBUG_INFO)
|
||
End Function
|
||
|
||
Public Sub WriteLine(s As String) Implements I_ConsoleDeviceHandle.WriteLine
|
||
Call _inputDevice.WriteLine(s)
|
||
Call _inputDevice.Flush()
|
||
End Sub
|
||
|
||
''' <summary>
|
||
''' 输出目标子进程的标准输出设备的内容
|
||
''' </summary>
|
||
''' <remarks></remarks>
|
||
Private Sub __listenSTDOUT()
|
||
Call $"{MethodBase.GetCurrentMethod.ToString} threading start! ProcessStateExit = {_processStateRunning.ToString}".__DEBUG_ECHO
|
||
|
||
Do While _processStateRunning = True
|
||
Call __tryGetSTDOUT()
|
||
Call Thread.Sleep(10)
|
||
Loop
|
||
|
||
Call $"{MethodBase.GetCurrentMethod.ToString}(""{Me.ProcessInfo.StartInfo.FileName.ToFileURL}"") threading exit!".__DEBUG_ECHO
|
||
End Sub
|
||
|
||
Private Function __tryGetSTDOUT() As String
|
||
Dim readBuffer As String = _consoleDevice.ReadToEnd '进程退出去了之后会卡在这里???
|
||
|
||
If Len(readBuffer) = 0 Then
|
||
Call Thread.Sleep(10)
|
||
Return ""
|
||
End If
|
||
|
||
Call __STDOUT_BUFFER.Append(readBuffer)
|
||
If _DispInvokedSTDOUT Then Call Console.WriteLine(readBuffer)
|
||
|
||
RaiseEvent DataArrival(readBuffer)
|
||
|
||
Return readBuffer
|
||
End Function
|
||
|
||
''' <summary>
|
||
''' 检测目标子进程是否已经结束
|
||
''' </summary>
|
||
''' <remarks></remarks>
|
||
Private Sub __detectProcessExit()
|
||
Call $"{MethodBase.GetCurrentMethod.ToString} threading start!".__DEBUG_ECHO
|
||
|
||
Do While Not ProcessInfo.HasExited
|
||
Call Thread.Sleep(10)
|
||
Loop
|
||
|
||
Call "Invoked process has been exit...".__DEBUG_ECHO
|
||
_processStateRunning = False
|
||
RaiseEvent ProcessExit(ProcessInfo.ExitCode, ProcessInfo.ExitTime.ToString)
|
||
End Sub
|
||
|
||
Public Overrides Function ToString() As String
|
||
Return String.Format("{0} {1}", ProcessInfo.StartInfo.FileName, ProcessInfo.StartInfo.Arguments)
|
||
End Function
|
||
|
||
''' <summary>
|
||
''' 在进行隐士转换的时候,假若可执行文件的文件路径之中含有空格,则这个时候应该要特别的小心
|
||
''' </summary>
|
||
''' <param name="CLI"></param>
|
||
''' <returns></returns>
|
||
Public Shared Widening Operator CType(CLI As String) As IORedirect
|
||
#If DEBUG Then
|
||
Call CLI.__DEBUG_ECHO
|
||
#End If
|
||
Dim Tokens As String() = Regex.Split(CLI, SPLIT_REGX_EXPRESSION)
|
||
Dim EXE As String = Tokens.First
|
||
Dim args As String = Mid$(CLI, Len(EXE) + 1)
|
||
|
||
Return New IORedirect(EXE, args)
|
||
End Operator
|
||
|
||
ReadOnly _IORedirect As Boolean
|
||
|
||
''' <summary>
|
||
''' Creates a <see cref="System.Diagnostics.Process"/> wrapper for the CLI program operations.
|
||
''' (在服务器上面可能会有一些线程方面的兼容性BUG的问题,不太清楚为什么会导致这样)
|
||
''' </summary>
|
||
''' <param name="Exe">The file path of the executable file.</param>
|
||
''' <param name="args">The CLI arguments for the folked program.</param>
|
||
''' <param name="envir">Set up the environment variable for the target invoked child process.</param>
|
||
''' <param name="_disp_debug"></param>
|
||
''' <param name="disp_STDOUT">是否显示目标被调用的外部程序的标准输出</param>
|
||
''' <remarks></remarks>
|
||
Public Sub New(Exe As String,
|
||
Optional args As String = "",
|
||
Optional envir As IEnumerable(Of KeyValuePair(Of String, String)) = Nothing,
|
||
Optional IORedirect As Boolean = True,
|
||
Optional disp_STDOUT As Boolean = True,
|
||
Optional _disp_debug As Boolean = False)
|
||
|
||
Dim pInfo As New ProcessStartInfo(Exe.GetString(""""c), args)
|
||
|
||
_IORedirect = IORedirect
|
||
pInfo.UseShellExecute = False
|
||
If IORedirect Then '只是重定向输出设备流
|
||
pInfo.RedirectStandardOutput = True
|
||
pInfo.RedirectStandardError = True
|
||
End If
|
||
pInfo.RedirectStandardInput = True
|
||
pInfo.ErrorDialog = False
|
||
pInfo.WindowStyle = ProcessWindowStyle.Hidden
|
||
pInfo.CreateNoWindow = True
|
||
|
||
If Not envir Is Nothing Then
|
||
For Each para As KeyValuePair(Of String, String) In envir
|
||
Call pInfo.EnvironmentVariables.Add(para.Key, para.Value)
|
||
Next
|
||
End If
|
||
|
||
_DispInvokedSTDOUT = disp_STDOUT
|
||
_ProcessInfo = New Process
|
||
_ProcessInfo.EnableRaisingEvents = True
|
||
_ProcessInfo.StartInfo = pInfo
|
||
_processStateRunning = False
|
||
|
||
If _disp_debug Then Call $"""{Exe}"" {args}".__DEBUG_ECHO
|
||
If _disp_debug Then Call Console.WriteLine(If(disp_STDOUT, "disp_STDOUT -> Yes!", "disp_STDOUT -> NO!"))
|
||
End Sub
|
||
|
||
Public Shared Function Shell(CommandLine As String) As IORedirect
|
||
Return CType(CommandLine, IORedirect)
|
||
End Function
|
||
|
||
Public Function Read() As Integer Implements I_ConsoleDeviceHandle.Read
|
||
Return _consoleDevice.Read
|
||
End Function
|
||
|
||
Public Function ReadLine() As String Implements I_ConsoleDeviceHandle.ReadLine
|
||
Return _consoleDevice.ReadLine
|
||
End Function
|
||
|
||
Public Sub WriteLine(s As String, ParamArray args() As String) Implements I_ConsoleDeviceHandle.WriteLine
|
||
Call Me._inputDevice.WriteLine(String.Format(s, args))
|
||
Call Me._inputDevice.Flush()
|
||
End Sub
|
||
|
||
#Region "IDisposable Support"
|
||
Private disposedValue As Boolean ' To detect redundant calls
|
||
|
||
' IDisposable
|
||
Protected Overridable Sub Dispose(disposing As Boolean)
|
||
If Not Me.disposedValue Then
|
||
If disposing Then
|
||
' TODO: dispose managed state (managed objects).
|
||
Call ProcessInfo.Close()
|
||
Call ProcessInfo.Dispose()
|
||
End If
|
||
|
||
' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
|
||
' TODO: set large fields to null.
|
||
End If
|
||
Me.disposedValue = True
|
||
End Sub
|
||
|
||
' TODO: override Finalize() only if Dispose( disposing As Boolean) above has code to free unmanaged resources.
|
||
'Protected Overrides Sub Finalize()
|
||
' ' Do not change this code. Put cleanup code in Dispose( disposing As Boolean) above.
|
||
' Dispose(False)
|
||
' MyBase.Finalize()
|
||
'End Sub
|
||
|
||
' This code added by Visual Basic to correctly implement the disposable pattern.
|
||
Public Sub Dispose() Implements IDisposable.Dispose
|
||
' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above.
|
||
Dispose(True)
|
||
GC.SuppressFinalize(Me)
|
||
End Sub
|
||
#End Region
|
||
|
||
''' <summary>
|
||
''' Gets the value that the associated process specified when it terminated.
|
||
''' </summary>
|
||
''' <param name="WaitForExit"></param>
|
||
''' <returns>The code that the associated process specified when it terminated.</returns>
|
||
Public Function Start(Optional WaitForExit As Boolean = False) As Integer Implements IIORedirectAbstract.Start
|
||
Return Start(WaitForExit, Nothing, True)
|
||
End Function
|
||
|
||
''' <summary>
|
||
''' 线程会被阻塞在这里,直到外部应用程序执行完毕
|
||
''' </summary>
|
||
''' <returns></returns>
|
||
Public Function Run() As Integer Implements IIORedirectAbstract.Run
|
||
Return Start(WaitForExit:=True)
|
||
End Function
|
||
End Class
|
||
End Namespace
|