core-worker
Version:
252 lines (220 loc) • 7.65 kB
JavaScript
/*
* This file is part of CoreWorker.
*
* CoreWorker 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.
*
* CoreWorker 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 CoreWorker. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2016 TeeAge-Beatz UG (haftungsbeschränkt)
*/
import Validator from "./Validator";
import NodeProcess from "./NodeProcess.type";
import Condition from "./Condition.type";
import T from "tcomb";
import { spawn } from "child_process";
import { assign, first, last } from "lodash";
import { EventEmitter } from "events";
import Result from "./Result";
import NotNil from "./NotNil.type";
import TimeoutError from "./TimeoutError";
import Q from "q";
import ExitCodes from "./ExitCodes.type";
/**
* Collects data and emits it afterwards
*
* @param {String} data received from process.output
*/
NodeProcess.prototype.validate = T.func([T.String], T.Nil, "nodeProcess.validate").of(function(data) {
if(Result.is(this.instance.result) || !this.filter.validate(data)) return;
const result = this.filter.getResult(data);
assign(this.instance, {
result: result,
fulfilled: true
});
this.emitter.emit("ready", result);
});
/**
* Stores new output
*
* @param {String} data to be stored
*/
NodeProcess.prototype.store = T.func([T.String], T.Nil, "nodeProcess.store").of(function(data) {
assign(this.instance, {
output: this.instance.output.concat([data])
});
});
/**
* Starts the Process of current instance
*
* @param {Index?} timeout after that a timeout event gets emitted
*/
NodeProcess.prototype.run = function(timeout = 0) {
if(this.isRunning()) return;
const process = spawn(this.command, this.commandArgs);
this.emitter.on("data", data => this.store(data.toString()));
this.emitter.on("data", data => this.validate(data.toString()));
process.stdout.on("data", data => this.emitter.emit("data", data.toString()));
process.stderr.on("data", data => this.emitter.emit("data", `<error> ${data}`));
process.on("close", (code, signal) => (
this.instance.exitCodes.indexOf(code) > -1 ||
NotNil.is(signal)
) ? ::this.finish(code) : ::this.terminate(code));
assign(this.instance, process, {
isRunning: true,
kill: process.kill
});
setTimeout(() => timeout > 0 ? this.emitter.emit("timeout") : null, timeout);
};
/**
* Streams data in process.stdin
*
* @param {Buffer} chunk piped in stdin
*/
NodeProcess.prototype.write = T.func([T.Object], T.Nil, "nodeProcess.write").of(function(chunk) {
this.instance.stdin.write(chunk);
});
/**
* Signals the instance, that it is streaming
*/
NodeProcess.prototype.startStream = T.func([], T.Nil, "nodeProcess.startStream").of(function() {
assign(this.instance, {
isStreaming: true
});
});
/**
* Flushs all data piped into stdin
*/
NodeProcess.prototype.end = T.func([], T.Nil, "nodeProcess.end").of(function() {
assign(this.instance, {
isStreaming: false
});
this.instance.stdin.end();
});
/**
* Kills a running instance
*
* @return {Promise}
*/
NodeProcess.prototype.kill = T.func([T.list(T.Number)], T.Object, "nodeProcess.kill").of(function(exitCodes) {
const deferred = Q.defer();
const onNotRunning = () => {
deferred.reject(new Error("Instance isn't running"));
return deferred.promise;
};
if(!this.isRunning) return onNotRunning();
this.onDeath(deferred, exitCodes);
this.instance.isStreaming && this.instance.stdin.pause(); // eslint-disable-line
this.instance.kill();
return deferred.promise;
});
/**
* Emits an Error if process was closed unexpectedly
*
* @param {Index} code as exit status of the instance
*/
NodeProcess.prototype.terminate = T.func([T.Number], T.Nil, "nodeProcess.terminate").of(function(code) {
assign(this.instance, { isRunning: false });
this.emitter.emit("failure", new Error(`Process was closed unexpectedly. Code: ${code}`));
});
/**
* Emits result after process was closed
*
* @param {number} code as exit status of the instance
*/
NodeProcess.prototype.finish = T.func([T.maybe(T.Number)], T.Nil, "nodeProcess.finish").of(function() {
assign(this.instance, { isRunning: false });
this.emitter.emit("death", Result.create(this.instance.output.join("")));
});
/**
* Stores a callback, that gets called, when process ends
*
* @param {Promise} deferred executed after Process was closed
*/
NodeProcess.prototype.onDeath = T.func([T.Object, ExitCodes], T.Nil, "nodeProcess.onDeath").of(function(deferred, exitCodes) {
assign(this.instance, { exitCodes: exitCodes });
this.emitter.on("death", deferred.resolve);
this.emitter.on("failure", deferred.reject);
});
/**
* Stores a callback, that gets called, when process is ready
*
* @param {Function} cb executed, when process is ready
*/
NodeProcess.prototype.onReady = T.func([T.Function], T.Nil, "nodeProcess.onReady").of(function(cb) {
this.emitter.on("ready", () => cb(this.instance));
});
/**
* Stores a callback, that gets called, when process logs in stdout/stderr
*
* @param {Function} cb executed on Output
*/
NodeProcess.prototype.onData = T.func([T.Function], T.Nil, "nodeProcess.onData").of(function(cb) {
this.emitter.on("data", cb);
});
/**
* Stores a deferrable that will be rejected on an emitted timeout event
*
* @param {Promise} deferred to be rejected on timeout event
*/
NodeProcess.prototype.onTimeout = T.func([T.Object], T.Nil, "nodeProcess.onTimeout").of(function(deferred) {
this.emitter.on("timeout", () => deferred.reject(TimeoutError.create(last(this.instance.output))));
});
/**
* Returns current state of Process
*
* @return {Boolean}
*/
NodeProcess.prototype.isRunning = T.func([], T.Boolean, "nodeProcess.isRunning").of(function() {
return this.instance.isRunning;
});
/**
* Returns if process is ready
*
* @return {Boolean}
*/
NodeProcess.prototype.isReady = T.func([], T.Boolean, "nodeProcess.isReady").of(function() {
return this.instance.fulfilled;
});
/**
* Returns last match
*
* @return {String?}
*/
NodeProcess.prototype.lastMatch = T.func([], T.maybe(T.String), "nodeProcess.lastMatch").of(function() {
return this.instance.lastMatch;
});
/**
* Creates a new Instance of NodeProcess
*
* @param {String} command which will be executed
* @param {Condition} condition to filter logs
* @return {NodeProcess}
*/
NodeProcess.create = T.func([T.String, Condition], NodeProcess, "NodeProcess.create").of(function(command, condition) {
const filter = Validator.create(condition);
const emitter = new EventEmitter();
const splittedCommand = command.split(" ");
return {
emitter: emitter,
command: first(splittedCommand),
commandArgs: splittedCommand.slice(1),
filter: filter,
instance: {
exitCodes: ExitCodes.create(),
isRunning: false,
isStreaming: false,
output: [],
lastMatch: null,
fulfilled: false
}
};
});
export default NodeProcess;