UNPKG

@empathize/framework

Version:

Framework for Neutralino

247 lines (201 loc) 7.07 kB
import path from '../paths/path.js'; import dir from '../paths/dir.js'; import Debug, { DebugThread } from '../meta/Debug.js'; declare const Neutralino; type ProcessOptions = { /** * Environment variables */ env?: object; /** * Current working directory for the running process */ cwd?: string; /** * Interval between tries to find started process id * * @default 50 */ childInterval?: number; }; class Process { /** * Process ID */ public readonly id: number; /** * Interval in ms between process status update * * null if you don't want to update process status * * @default 200 */ public runningInterval: number|null = 200; /** * Interval in ms between process output update * * null if you don't want to update process output * * @default 500 */ public outputInterval: number|null = 500; protected outputFile: string|null; protected outputOffset: number = 0; protected _finished: boolean = false; /** * Whether the process was finished */ public get finished(): boolean { return this._finished; }; protected onOutput?: (output: string, process: Process) => void; protected onFinish?: (process: Process) => void; public constructor(pid: number, outputFile: string|null = null) { this.id = pid; this.outputFile = outputFile; const debugThread = new DebugThread('Process/Stream', `Opened process ${pid} stream`); const updateStatus = () => { this.running().then((running) => { // The process is still running if (running) { if (this.runningInterval) setTimeout(updateStatus, this.runningInterval); } // Otherwise the process was stopped else { this._finished = true; debugThread.log('Process stopped'); if (this.onFinish) this.onFinish(this); } }); }; if (this.runningInterval) setTimeout(updateStatus, this.runningInterval); if (this.outputFile) { const updateOutput = () => { Neutralino.filesystem.readFile(this.outputFile) .then((output: string) => { if (this.onOutput) this.onOutput(output.substring(this.outputOffset), this); this.outputOffset = output.length; if (this._finished) Neutralino.filesystem.removeFile(this.outputFile); else if (this.outputInterval) setTimeout(updateOutput, this.outputInterval); }) .catch(() => { if (this.outputInterval && !this._finished) setTimeout(updateOutput, this.outputInterval); }); }; if (this.outputInterval) setTimeout(updateOutput, this.outputInterval); } } /** * Specify callback to run when the process will be finished */ public finish(callback: (process: Process) => void) { this.onFinish = callback; if (this._finished) callback(this); // If user stopped process status auto-checking // then we should check it manually when this method was called else if (this.runningInterval === null) { this.running().then((running) => { if (!running) { this._finished = true; callback(this); } }); } } public output(callback: (output: string, process: Process) => void) { this.onOutput = callback; } /** * Kill process */ public kill(forced: boolean = false): Promise<void> { Neutralino.filesystem.removeFile(this.outputFile); return Process.kill(this.id, forced); } /** * Returns whether the process is running * * This method doesn't call onFinish event */ public running(): Promise<boolean> { return new Promise((resolve) => { Neutralino.os.execCommand(`ps -p ${this.id} -S`).then((output) => { resolve(output.stdOut.includes(this.id) && !output.stdOut.includes('Z ')); }); }); } /** * Run shell command */ public static run(command: string, options: ProcessOptions = {}): Promise<Process> { return new Promise(async (resolve) => { const tmpFile = `${await dir.temp}/${10000 + Math.round(Math.random() * 89999)}.tmp`; // Set env variables if (options.env) for (const key of Object.keys(options.env)) command = `${key}="${path.addSlashes(options.env![key].toString())}" ${command}`; // Set output redirection to the temp file command = `${command} > "${path.addSlashes(tmpFile)}" 2>&1`; // Set current working directory if (options.cwd) command = `cd "${path.addSlashes(options.cwd)}" && ${command}`; // And run the command const process = await Neutralino.os.execCommand(command, { background: true }); const childFinder = async () => { const childProcess = await Neutralino.os.execCommand(`pgrep -P ${process.pid}`); // Child wasn't found if (childProcess.stdOut == '') setTimeout(childFinder, options.childInterval ?? 50); // Otherwise return its id else { const processId = parseInt(childProcess.stdOut.substring(0, childProcess.stdOut.length - 1)); Debug.log({ function: 'Process.run', message: { 'running command': command, 'cwd': options.cwd, 'initial process id': process.pid, 'real process id': processId, ...options.env } }); resolve(new Process(processId, tmpFile)); } }; setTimeout(childFinder, options.childInterval ?? 50); }); } public static kill(id: number, forced: boolean = false): Promise<void> { return new Promise((resolve) => { Neutralino.os.execCommand(`kill ${forced ? '-9' : '-15'} ${id}`).then(() => resolve()); }); } } export type { ProcessOptions }; export default Process;