import child_process = require ( 'child_process' ) ; import fs = require ( 'fs' ) ; import os = require ( 'os' ) ; import path = require ( 'path' ) ; import { Root } from './root' ; import eventBus from './eventBus' ; import request = require ( 'request' ) ; import _ = require ( 'underscore' ) ; import { Feedback } from './feedback' ; import { MyExecError , MyHttpError , MyError } from './errors' ; import { Terminal } from 'vscode' ; const psList = require ( 'ps-list' ) ; export class ProcessFacade { public static execute ( options ) { var resolve , reject ; let promise = new Promise ( ( _resolve , _reject ) => { resolve = _resolve ; reject = _reject ; } ) ; let program = options . program ; let programName = FsFacade . getFilename ( program ) ; let workingDirectory = options . workingDirectory ; let args = options . args ; let environment = options . environment ; let eventTag = options . eventTag ; let channels = options . channels || [ "default" ] ; let stdoutToFile = options . stdoutToFile ; let doNotDumpStdout = options . doNotDumpStdout ; let collectStdOut = options . collectStdOut ; let spawnOptions = { cwd : workingDirectory , env : environment } ; Feedback . debug ( `Execute [${ program }] with arguments ${ JSON . stringify ( args ) }` , channels ) ; if ( workingDirectory ) { Feedback . debug ( `Working directory: ${ workingDirectory }` , channels ) ; } if ( environment ) { Feedback . debug ( `Environment variables:` , channels ) ; Feedback . debug ( JSON . stringify ( environment , null , 4 ) , channels ) ; } let subprocess = child_process . spawn ( program , args , spawnOptions ) ; subprocess . on ( "error" , function ( error ) { reject ( new MyExecError ( { Program : programName , Message : error . message } ) ) ; } ) ; let latestStdout = "" ; let collectedStdout = "" ; let latestStderr = "" ; subprocess . stdout . setEncoding ( 'utf8' ) ; subprocess . stderr . setEncoding ( 'utf8' ) ; if ( stdoutToFile ) { let writeStream = fs . createWriteStream ( stdoutToFile ) ; subprocess . stdout . pipe ( writeStream ) ; } if ( eventTag ) { eventBus . emit ( `${ eventTag }:started` , { program : program , args : args } ) ; } subprocess . stdout . on ( "data" , function ( data ) { latestStdout = data ; if ( collectStdOut ) { collectedStdout += data ; } if ( ! doNotDumpStdout ) { Feedback . programOutput ( programName , data , channels ) ; } if ( options . onOutput ) { options . onOutput ( data ) ; } if ( eventTag ) { eventBus . emit ( `${ eventTag }:output` , data ) ; } } ) ; subprocess . stderr . on ( "data" , function ( data ) { latestStderr = data ; Feedback . programOutput ( programName , data , channels ) ; if ( options . onError ) { options . onError ( data ) ; } if ( eventTag ) { eventBus . emit ( `${ eventTag }:error` , data ) ; } } ) ; subprocess . on ( "close" , function ( code ) { Feedback . debug ( `[${ programName }] exists, exit code = ${ code }.` , channels ) ; let stdout = ( collectedStdout || latestStdout ) . trim ( ) ; if ( options . onClose ) { options . onClose ( code , stdout ) ; } if ( eventTag ) { eventBus . emit ( `${ eventTag }:close` , code ) ; } if ( code == 0 ) { resolve ( { code : code , stdout : stdout } ) ; } else { reject ( new MyExecError ( { Program : programName , Message : latestStderr , Code : code . toString ( ) } ) ) ; } } ) ; return promise ; } } export class FsFacade { public static createTempFile ( fileName , content ) { let filePath = path . join ( os . tmpdir ( ) , fileName ) ; fs . writeFileSync ( filePath , content ) ; return filePath ; } public static removeExtension ( filePath ) { let parsedPath = path . parse ( filePath ) ; let withoutExtension = path . join ( parsedPath . dir , parsedPath . name ) ; return withoutExtension ; } public static getExtension ( filePath ) { return path . extname ( filePath ) ; } public static getTopmostFolder ( filePath ) { let parts = filePath . split ( path . sep ) . filter ( item => item . length > 0 ) ; return parts [ 0 ] ; } public static getFilename ( filePath ) { return path . basename ( filePath ) ; } public static getFilenameWithoutExtension ( filePath ) { let fileName = path . basename ( filePath ) ; return FsFacade . removeExtension ( fileName ) ; } public static getFolder ( filePath ) { return path . dirname ( filePath ) ; } public static readFile ( filePath ) { if ( ! FsFacade . fileExists ( filePath ) ) { throw new MyError ( { Message : `Missing file: ${ filePath }` } ) ; } let text = fs . readFileSync ( filePath , { encoding : "utf8" } ) ; return text ; } public static readBinaryFile ( filePath ) { let buffer = fs . readFileSync ( filePath ) ; return buffer ; } public static readFileInContent ( filePath ) { filePath = path . join ( FsFacade . getPathToContent ( ) , filePath ) ; return FsFacade . readFile ( filePath ) ; } public static getPathToContent ( ) { let extensionPath = Root . ExtensionContext . extensionPath ; return path . join ( extensionPath , "content" ) ; } public static writeFile ( filePath , content ) { fs . writeFileSync ( filePath , content ) ; } public static fileExists ( filePath ) { return fs . existsSync ( filePath ) ; } public static readLatestFileInFolder ( ... pathParts ) { let latest = FsFacade . getLatestFileInFolder ( ... pathParts ) ; let content = FsFacade . readFile ( latest ) ; return content ; } public static getLatestFileInFolder ( ... pathParts ) { let folder = path . join ( ... pathParts ) ; let files = fs . readdirSync ( folder ) ; let latest = _ . max ( files , function ( fileName ) { let fullpath = path . join ( folder , fileName ) ; return fs . statSync ( fullpath ) . mtime ; } ) ; let fullpath = path . join ( folder , latest ) ; return fullpath ; } public static markAsExecutable ( filePath ) { Feedback . debug ( `markAsExecutable(${ filePath })` ) ; fs . chmodSync ( filePath , "755" ) ; } public static unzip ( archivePath , destinationFolder ) { Feedback . debug ( `unzip ${ archivePath } to ${ destinationFolder }.` ) ; return ProcessFacade . execute ( { program : "unzip" , args : [ "-o" , archivePath , "-d" , destinationFolder ] } ) ; } public static untar ( archivePath , destinationFolder ) { Feedback . debug ( `untar ${ archivePath } to ${ destinationFolder }.` ) ; return ProcessFacade . execute ( { program : "tar" , args : [ "-C" , destinationFolder , "-xzf" , archivePath ] } ) ; } public static createFolderIfNotExists ( folderPath ) { let parentFolder = path . dirname ( folderPath ) ; if ( ! fs . existsSync ( parentFolder ) ) { throw new Error ( `Parent folder ${ parentFolder } does not exist.` ) ; } if ( ! fs . existsSync ( folderPath ) ) { Feedback . debug ( `Creating folder: ${ folderPath }` ) ; fs . mkdirSync ( folderPath ) ; } } public static mkDirByPathSync ( targetDir ) { const sep = path . sep ; const initDir = path . isAbsolute ( targetDir ) ? sep : '' ; const baseDir = '.' ; return targetDir . split ( sep ) . reduce ( ( parentDir , childDir ) => { const curDir = path . resolve ( baseDir , parentDir , childDir ) ; try { fs . mkdirSync ( curDir ) ; } catch ( err ) { if ( err . code === 'EEXIST' ) { return curDir ; } if ( err . code === 'ENOENT' ) { throw new Error ( `EACCES: permission denied, mkdir '${ parentDir }'` ) ; } const caughtErr = [ 'EACCES' , 'EPERM' , 'EISDIR' ] . indexOf ( err . code ) > - 1 ; if ( ! caughtErr || caughtErr && curDir === path . resolve ( targetDir ) ) { throw err ; } } return curDir ; } , initDir ) ; } public static copyFile ( source , destination ) { Feedback . debug ( `copy: ${ source } TO ${ destination }` ) ; fs . copyFileSync ( source , destination ) ; } public static copyFolder ( sourceFolder , destinationFolder ) { child_process . execSync ( `cp -r ${ sourceFolder }/* ${ destinationFolder }` ) ; } public static listFolder ( parentFolder ) { let folders = fs . readdirSync ( parentFolder ) . filter ( child => fs . statSync ( path . join ( parentFolder , child ) ) . isDirectory ( ) ) ; return folders ; } } export class RestFacade { public static post ( options ) { var resolve , reject ; let promise = new Promise ( ( _resolve , _reject ) => { resolve = _resolve ; reject = _reject ; } ) ; let url = options . url ; let data = options . data ; let eventTag = options . eventTag ; let channels = options . channels || [ "http" ] ; let requestOptions = { json : data } ; if ( eventTag ) { eventBus . emit ( `${ eventTag }:request` , { url : url , data : data } ) ; } Feedback . debug ( `http post, ${ url }` , channels ) ; Feedback . debug ( JSON . stringify ( options , null , 4 ) , channels ) ; request . post ( url , requestOptions , function ( error , response , body ) { let statusCode = response ? response . statusCode : null ; let isErrorneous = error || statusCode == 500 ; if ( isErrorneous ) { eventBus . emit ( `${ eventTag }:error` , { url : url , data : error } ) ; reject ( new MyHttpError ( { Url : url , RequestError : error } ) ) ; } else { eventBus . emit ( `${ eventTag }:response` , { url : url , data : body } ) ; Feedback . debug ( `http post, status=${ statusCode }, response:` , channels ) ; Feedback . debug ( JSON . stringify ( body , null , 4 ) , channels ) ; if ( statusCode != 200 ) { Feedback . error ( "Errorneous HTTP response, please check." ) ; } resolve ( body ) ; } } ) ; return promise ; } public static download ( options ) { const waitBeforeCloseStream = 500 ; var resolve , reject ; let promise = new Promise ( ( _resolve , _reject ) => { resolve = _resolve ; reject = _reject ; } ) ; let url = options . url ; let destination = options . destination ; let writeStream = fs . createWriteStream ( destination ) ; Feedback . debug ( `Downloading: ${ url }` ) ; Feedback . debug ( `Destination: ${ destination }` ) ; let contentLength = Number . MAX_SAFE_INTEGER ; let downloaded = 0 ; let progress = 0 ; let percentage = 0 ; let previousPercentage = - 1 ; request . get ( url ) . on ( "response" , function ( response ) { contentLength = parseInt ( response . headers [ 'content-length' ] ) ; } ) . on ( "data" , function ( chunk ) { downloaded += chunk . length ; progress = downloaded / contentLength ; percentage = Math . round ( progress * 100 ) ; if ( percentage != previousPercentage ) { eventBus . emit ( "download" , { url : url , file : destination , progress : progress , percentage : percentage , downloaded : downloaded , length : contentLength } ) ; } previousPercentage = percentage ; } ) . on ( "error" , function ( error ) { writeStream . close ( ) ; reject ( new MyHttpError ( { Url : url , RequestError : error } ) ) ; } ) . on ( "complete" , function ( response ) { let statusCode = response . statusCode ; let statusMessage = response . statusMessage ; if ( statusCode == 200 ) { setTimeout ( function ( ) { writeStream . close ( ) ; Feedback . debug ( `Downloaded: ${ destination }.` ) ; resolve ( ) ; } , waitBeforeCloseStream ) ; eventBus . emit ( "download" , { url : url , file : destination , progress : 1.0 , percentage : 100 , downloaded : downloaded , length : contentLength } ) ; } else { setTimeout ( function ( ) { writeStream . close ( ) ; reject ( new MyHttpError ( { Url : url , Message : statusMessage , Code : statusCode . toString ( ) } ) ) ; } , waitBeforeCloseStream ) ; eventBus . emit ( "download" , { url : url , file : destination , progress : 0.0 , percentage : 0 , downloaded : 0 , length : contentLength } ) ; } } ) . pipe ( writeStream ) ; return promise ; } } export async function waitForProcessInTerminal ( terminal ) { let pid = await terminal . processId ; try { while ( true ) { await sleep ( 250 ) ; let result = await psList ( { all : true } ) ; let hasChildren = result . some ( item => item . ppid == pid ) ; if ( ! hasChildren ) { break ; } } } catch ( error ) { if ( error instanceof MyExecError ) { if ( error . Message . length == 0 && Number ( error . Code ) == 1 ) { return ; } } throw error ; } } export async function sleep ( milliseconds ) { return new Promise ( resolve => setTimeout ( resolve , milliseconds ) ) ; }