import { spawn , ChildProcess } from 'child_process' ; import { EventEmitter } from 'events' ; import { IPCMessages as IPC , Subscription } from '../../services/service.electron' ; import ServiceElectron from '../../services/service.electron' ; import ServiceStreamSource from '../../services/service.stream.sources' ; import ServiceStreams from '../../services/service.streams' ; import Logger from '../../tools/env.logger' ; export interface IForkSettings { env : { [ key ] : string ; } ; shell : string | boolean ; pwd : string ; } export interface ICommand { cmd : string ; guid : string ; settings : IForkSettings ; } export default class Process extends EventEmitter { public static Events = { destroy : 'destroy' , } ; public Events = Process . Events ; private _session : string ; private _process : ChildProcess | undefined ; private _command : ICommand ; private _subscriptions : { [ key ] : Subscription ; } = { } ; private _logger : Logger ; private _stat : IPC . IShellProcessStat = { created : 0 , recieved : 0 , terminated : 0 , pid : 0 , } ; private _meta : IPC . IShellProcessMeta = { sourceId : 0 , color : '' , } ; constructor ( session , command ) { super ( ) ; this . _session = session ; this . _command = command ; this . _logger = new Logger ( `Shell process "${ command . guid }"` ) ; ServiceElectron . IPC . subscribe ( IPC . ShellProcessDetailsRequest , this . _ipc_ShellProcessDetailsRequest . bind ( this ) as any ) . then ( ( subscription ) => { this . _subscriptions . ShellProcessDetailsRequest = subscription ; } ) . catch ( ( err ) => this . _logger . error ( `Fail to subscribe to ShellProcessDetailsRequest due error: ${ err . message }` ) ) ; ServiceElectron . IPC . subscribe ( IPC . ShellProcessKillRequest , this . _ipc_ShellProcessKillRequest . bind ( this ) as any ) . then ( ( subscription ) => { this . _subscriptions . ShellProcessKillRequest = subscription ; } ) . catch ( ( err ) => this . _logger . error ( `Fail to subscribe to ShellProcessKillRequest due error: ${ err . message }` ) ) ; } public execute ( ) { if ( this . _process !== undefined ) { return this . _logger . error ( `Attempt to start process, which already was started` ) ; } const len = ServiceStreams . getStreamLen ( this . _session ) ; if ( len instanceof Error ) { this . _logger . error ( `Fail to get length of session ${ this . _session } due error: ${ len . message }` ) ; this . _stat . row = - 1 ; } else { this . _stat . row = len ; } this . _meta . sourceId = ServiceStreamSource . add ( { name : this . _command . cmd , session : this . _session , } ) ; this . _process = spawn ( this . _command . cmd , { cwd : this . _command . settings . pwd , env : this . _command . settings . env , shell : this . _command . settings . shell , } ) ; this . _stat . created = Date . now ( ) ; this . _stat . pid = this . _process . pid === undefined ? 0 : this . _process . pid ; this . _stat . recieved = 0 ; this . _process . stdout !== null && this . _process . stdout . on ( 'data' , this . _onData . bind ( this ) ) ; this . _process . stderr !== null && this . _process . stderr . on ( 'data' , this . _onData . bind ( this ) ) ; this . _process . on ( 'exit' , this . _onDone . bind ( this ) ) ; this . _process . on ( 'close' , this . _onDone . bind ( this ) ) ; this . _process . on ( 'disconnect' , this . _onDone . bind ( this ) ) ; this . _process . on ( 'error' , this . _onDone . bind ( this ) ) ; ServiceElectron . IPC . send ( new IPC . ShellProcessStartedEvent ( { session : this . _session , guid : this . _command . guid , } ) ) . catch ( ( err ) => this . _logger . warn ( `Fail to send ShellProcessStartedEvent due error ${ err . message }` ) ) ; } public getInfo ( ) { return { guid : this . _command . guid , command : this . _command . cmd , stat : this . _stat , meta : this . _meta , env : this . _command . settings . env , pwd : this . _command . settings . pwd , } ; } public destroy ( ) { if ( this . _process === undefined ) { return new Error ( this . _logger . error ( `Attempt to destroy child process, which isn't created or was destroyed.` ) ) ; } this . _process . removeAllListeners ( ) ; if ( this . _process . stdout !== null ) { this . _process . stdout . removeAllListeners ( ) ; this . _process . stdout . destroy ( ) ; } if ( this . _process . stderr !== null ) { this . _process . stderr . removeAllListeners ( ) ; this . _process . stderr . destroy ( ) ; } this . _process . kill ( ) ; this . _process = undefined ; this . emit ( this . Events . destroy ) ; this . removeAllListeners ( ) ; } private _onData ( chunk ) { if ( this . _process === undefined ) { return ; } if ( typeof chunk === 'string' ) { this . _stat . recieved += chunk . length ; } else if ( chunk instanceof Buffer ) { this . _stat . recieved += chunk . byteLength ; } ServiceStreams . writeTo ( chunk , this . _meta . sourceId , undefined , this . _session ) . catch ( ( err ) => { this . _logger . error ( `Fail to write a chunk into stream ${ this . _session }` ) ; } ) ; } private _onDone ( error ? ) { if ( error instanceof Error ) { this . _onData ( error . message ) ; } this . destroy ( ) ; } private _ipc_ShellProcessKillRequest ( request , response ) { if ( request . guid !== this . _command . guid ) { return ; } const error = this . destroy ( ) ; response ( new IPC . ShellProcessKillResponse ( { error : error !== undefined ? error . message : undefined , } ) ) ; } private _ipc_ShellProcessDetailsRequest ( request , response ) { if ( request . guid !== this . _command . guid ) { return ; } response ( new IPC . ShellProcessDetailsResponse ( { info : this . getInfo ( ) , } ) ) ; } }