| 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 |