commit db61d35c4495f9474c4bbe618a56083fcfc7bd5d Author: Mees van der Wijk Date: Wed Oct 22 22:08:30 2025 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..096746c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a871228 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}\\dist\\Main.js", + "args": [ + // "--no-output-log" + ] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1cab2be --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# NTSH Camera Runner + +A process monitoring service for the Nothing to See Here installation, designed to work with the main [NTSH-Control](https://git.morphix.nl/Nothing-to-See-Here/NTSH-Control) system. + +## Purpose + +Camera Runner monitors and manages the camera executable process, providing: + +- Automatic process startup and crash recovery +- WebSocket communication on port `6301` for integration with [NTSH-Control](https://git.morphix.nl/Nothing-to-See-Here/NTSH-Control) +- Real-time process status and output logging +- Remote restart and system reboot capabilities + +## Configuration + +Configuration is stored in `~/MorphixProductions/NTSHControl/CameraRunner/config.json`: + +```json +{ + "socketServer": { + "port": 6301 + }, + "executable": { + "path": "", + "arguments": [] + } +} +``` + +## Integration + +This service is controlled by the main [NTSH-Control](https://git.morphix.nl/Nothing-to-See-Here/NTSH-Control) system via WebSocket communication. The main control system connects to `127.0.0.1:6301` to monitor status and send restart commands. + +## Process States + +- `STARTING` - Process launching +- `RUNNING` - Process active and healthy +- `STOPPED` - Process terminated or crashed +- `PROBLEM` - Error condition detected diff --git a/dist/Configuration/ConfigurationManager.js b/dist/Configuration/ConfigurationManager.js new file mode 100644 index 0000000..6d15f27 --- /dev/null +++ b/dist/Configuration/ConfigurationManager.js @@ -0,0 +1,138 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ConfigurationManager = void 0; +var fs_extra_1 = require("fs-extra"); +var path_1 = require("path"); +var DefaultConfiguration_1 = require("./DefaultConfiguration"); +var PREFIX = '[ConfigurationManager]'; +var ConfigurationManager = /** @class */ (function () { + function ConfigurationManager(Main) { + this._Main = Main; + } + ConfigurationManager.prototype.load = function () { + return __awaiter(this, void 0, void 0, function () { + var configPath, configExists, config, _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + console.log(PREFIX, 'Loading'); + return [4 /*yield*/, (0, fs_extra_1.ensureDir)((0, path_1.join)(this._Main.dataPath))]; + case 1: + _b.sent(); + configPath = (0, path_1.join)(this._Main.dataPath, 'config.json'); + return [4 /*yield*/, (0, fs_extra_1.pathExists)(configPath)]; + case 2: + configExists = _b.sent(); + if (!!configExists) return [3 /*break*/, 4]; + return [4 /*yield*/, (0, fs_extra_1.writeFile)(configPath, JSON.stringify(DefaultConfiguration_1.DefaultConfiguration, null, 4))]; + case 3: + _b.sent(); + this._Main.Config = DefaultConfiguration_1.DefaultConfiguration; + console.log(PREFIX, 'Written default configuration'); + return [3 /*break*/, 7]; + case 4: return [4 /*yield*/, (0, fs_extra_1.readJSON)(configPath)]; + case 5: + config = _b.sent(); + _a = this._Main; + return [4 /*yield*/, this.validateConfig(config)]; + case 6: + _a.Config = _b.sent(); + console.log(PREFIX, 'Loaded configuration'); + _b.label = 7; + case 7: return [2 /*return*/]; + } + }); + }); + }; + ConfigurationManager.prototype.validateConfig = function (config) { + return __awaiter(this, void 0, void 0, function () { + var normalizedConfig, hasChanges, configPath; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + normalizedConfig = this.normalizeConfig(config, DefaultConfiguration_1.DefaultConfiguration); + hasChanges = JSON.stringify(config) !== JSON.stringify(normalizedConfig); + if (!hasChanges) return [3 /*break*/, 2]; + this._Main.Config = normalizedConfig; + configPath = (0, path_1.join)(this._Main.dataPath, 'config.json'); + return [4 /*yield*/, (0, fs_extra_1.writeFile)(configPath, JSON.stringify(normalizedConfig, null, 4))]; + case 1: + _a.sent(); + console.log(PREFIX, 'Configuration updated and saved'); + _a.label = 2; + case 2: return [2 /*return*/, normalizedConfig]; + } + }); + }); + }; + ConfigurationManager.prototype.normalizeConfig = function (current, template) { + var _this = this; + if (template === null || template === undefined) { + return template; + } + if (typeof template !== 'object' || Array.isArray(template)) { + if (current !== null && + current !== undefined && + typeof current === typeof template) { + if (Array.isArray(template) && Array.isArray(current)) { + if (template.length > 0 && + typeof template[0] === 'object' && + !Array.isArray(template[0])) { + return current.map(function (item) { + return _this.normalizeConfig(item, template[0]); + }); + } + return current; + } + return current; + } + return template; + } + var result = {}; + for (var key in template) { + if (template.hasOwnProperty(key)) { + result[key] = this.normalizeConfig(current === null || current === void 0 ? void 0 : current[key], template[key]); + } + } + return result; + }; + return ConfigurationManager; +}()); +exports.ConfigurationManager = ConfigurationManager; +//# sourceMappingURL=ConfigurationManager.js.map \ No newline at end of file diff --git a/dist/Configuration/ConfigurationManager.js.map b/dist/Configuration/ConfigurationManager.js.map new file mode 100644 index 0000000..6f327f6 --- /dev/null +++ b/dist/Configuration/ConfigurationManager.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ConfigurationManager.js","sourceRoot":"","sources":["../../src/Configuration/ConfigurationManager.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/Configuration/DefaultConfiguration.js b/dist/Configuration/DefaultConfiguration.js new file mode 100644 index 0000000..0a75654 --- /dev/null +++ b/dist/Configuration/DefaultConfiguration.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DefaultConfiguration = void 0; +exports.DefaultConfiguration = { + socketServer: { + port: 6301, + }, + executable: { + path: '', + arguments: [], + }, +}; +//# sourceMappingURL=DefaultConfiguration.js.map \ No newline at end of file diff --git a/dist/Configuration/DefaultConfiguration.js.map b/dist/Configuration/DefaultConfiguration.js.map new file mode 100644 index 0000000..b6015bf --- /dev/null +++ b/dist/Configuration/DefaultConfiguration.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DefaultConfiguration.js","sourceRoot":"","sources":["../../src/Configuration/DefaultConfiguration.ts"],"names":[],"mappings":";;;AAEa,QAAA,oBAAoB,GAAW;IAC3C,YAAY,EAAE;QACb,IAAI,EAAE,IAAI;KACV;IACD,UAAU,EAAE;QACX,IAAI,EAAE,EAAE;QACR,SAAS,EAAE,EAAE;KACb;CACD,CAAC"} \ No newline at end of file diff --git a/dist/Main.js b/dist/Main.js new file mode 100644 index 0000000..0707b96 --- /dev/null +++ b/dist/Main.js @@ -0,0 +1,79 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Main = void 0; +var path_1 = require("path"); +var ConfigurationManager_1 = require("./Configuration/ConfigurationManager"); +var os_1 = require("os"); +var SocketServer_1 = require("./SocketServer"); +var processWatcher_1 = require("./processWatcher"); +var Utils_1 = require("./Utils"); +var Main = /** @class */ (function () { + function Main() { + this.dataPath = (0, path_1.join)((0, os_1.homedir)(), 'MorphixProductions', 'NTSHControl', 'CameraRunner'); + this.ConfigurationManager = new ConfigurationManager_1.ConfigurationManager(this); + this.SocketServer = new SocketServer_1.SocketServer(this); + this.ProcessWatcher = new processWatcher_1.ProcessWatcher(this); + } + Main.prototype.start = function () { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, this.ConfigurationManager.load()]; + case 1: + _a.sent(); + return [4 /*yield*/, this.SocketServer.listen()]; + case 2: + _a.sent(); + return [4 /*yield*/, (0, Utils_1.delay)(5000)]; + case 3: + _a.sent(); + return [4 /*yield*/, this.ProcessWatcher.start()]; + case 4: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + return Main; +}()); +exports.Main = Main; +var _Main = new Main(); +_Main.start(); +//# sourceMappingURL=Main.js.map \ No newline at end of file diff --git a/dist/Main.js.map b/dist/Main.js.map new file mode 100644 index 0000000..258848b --- /dev/null +++ b/dist/Main.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Main.js","sourceRoot":"","sources":["../src/Main.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6BAA4B;AAC5B,6EAG8C;AAC9C,yBAA6B;AAC7B,+CAA8C;AAC9C,mDAAkD;AAClD,iCAAgC;AAEhC;IAAA;QACC,aAAQ,GAAG,IAAA,WAAI,EACd,IAAA,YAAO,GAAE,EACT,oBAAoB,EACpB,aAAa,EACb,cAAc,CACd,CAAC;QAEF,yBAAoB,GAAG,IAAI,2CAAoB,CAAC,IAAI,CAAC,CAAC;QACtD,iBAAY,GAAG,IAAI,2BAAY,CAAC,IAAI,CAAC,CAAC;QACtC,mBAAc,GAAG,IAAI,+BAAc,CAAC,IAAI,CAAC,CAAC;IAY3C,CAAC;IARM,oBAAK,GAAX;;;;4BACC,qBAAM,IAAI,CAAC,oBAAoB,CAAC,IAAI,EAAE,EAAA;;wBAAtC,SAAsC,CAAC;wBACvC,qBAAM,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAA;;wBAAhC,SAAgC,CAAC;wBAEjC,qBAAM,IAAA,aAAK,EAAC,IAAI,CAAC,EAAA;;wBAAjB,SAAiB,CAAC;wBAElB,qBAAM,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,EAAA;;wBAAjC,SAAiC,CAAC;;;;;KAClC;IACF,WAAC;AAAD,CAAC,AAtBD,IAsBC;AAtBY,oBAAI;AAwBjB,IAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC;AACzB,KAAK,CAAC,KAAK,EAAE,CAAC"} \ No newline at end of file diff --git a/dist/Reboot.js b/dist/Reboot.js new file mode 100644 index 0000000..2f4121c --- /dev/null +++ b/dist/Reboot.js @@ -0,0 +1,49 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.reboot = reboot; +var child_process_1 = require("child_process"); +function reboot() { + if (process.platform === 'win32') { + return rebootWindows(); + } + else if (process.platform === 'linux') { + return rebootLinux(); + } + return Promise.resolve({ + succeed: false, + message: 'Platform not supported', + }); +} +function rebootWindows() { + return new Promise(function (resolve, reject) { + (0, child_process_1.exec)('shutdown /r /t 3', function (error, stdout, stderr) { + if (error) { + console.error("Error shutting down Windows: ".concat(error.message)); + return resolve({ succeed: false, message: error.message }); + } + if (stderr) { + console.error("Error shutting down Windows: ".concat(stderr)); + return resolve({ succeed: false, message: stderr }); + } + console.log("Windows shutdown command executed: ".concat(stdout)); + resolve({ succeed: true }); + }); + }); +} +function rebootLinux() { + return new Promise(function (resolve, reject) { + (0, child_process_1.exec)('shutdown -r now', function (error, stdout, stderr) { + if (error) { + console.error("Error shutting down Linux: ".concat(error.message)); + return resolve({ succeed: false, message: error.message }); + } + if (stderr) { + console.error("Error shutting down Linux: ".concat(stderr)); + return resolve({ succeed: false, message: stderr }); + } + console.log("Linux shutdown command executed: ".concat(stdout)); + resolve({ succeed: true }); + }); + }); +} +//# sourceMappingURL=Reboot.js.map \ No newline at end of file diff --git a/dist/Reboot.js.map b/dist/Reboot.js.map new file mode 100644 index 0000000..c557557 --- /dev/null +++ b/dist/Reboot.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Reboot.js","sourceRoot":"","sources":["../src/Reboot.ts"],"names":[],"mappings":";;AAEA,wBAWC;AAbD,+CAAqC;AAErC,SAAgB,MAAM;IACrB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAClC,OAAO,aAAa,EAAE,CAAC;IACxB,CAAC;SAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzC,OAAO,WAAW,EAAE,CAAC;IACtB,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,CAAC;QACtB,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,wBAAwB;KACjC,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,aAAa;IACrB,OAAO,IAAI,OAAO,CACjB,UAAC,OAAO,EAAE,MAAM;QACf,IAAA,oBAAI,EAAC,kBAAkB,EAAE,UAAC,KAAK,EAAE,MAAM,EAAE,MAAM;YAC9C,IAAI,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CACZ,uCAAgC,KAAK,CAAC,OAAO,CAAE,CAC/C,CAAC;gBACF,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,uCAAgC,MAAM,CAAE,CAAC,CAAC;gBACxD,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACrD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,6CAAsC,MAAM,CAAE,CAAC,CAAC;YAC5D,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACJ,CAAC,CACD,CAAC;AACH,CAAC;AAED,SAAS,WAAW;IACnB,OAAO,IAAI,OAAO,CACjB,UAAC,OAAO,EAAE,MAAM;QACf,IAAA,oBAAI,EAAC,iBAAiB,EAAE,UAAC,KAAK,EAAE,MAAM,EAAE,MAAM;YAC7C,IAAI,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CACZ,qCAA8B,KAAK,CAAC,OAAO,CAAE,CAC7C,CAAC;gBACF,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,qCAA8B,MAAM,CAAE,CAAC,CAAC;gBACtD,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACrD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,2CAAoC,MAAM,CAAE,CAAC,CAAC;YAC1D,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACJ,CAAC,CACD,CAAC;AACH,CAAC"} \ No newline at end of file diff --git a/dist/Shutdown.js b/dist/Shutdown.js new file mode 100644 index 0000000..b14740a --- /dev/null +++ b/dist/Shutdown.js @@ -0,0 +1,49 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.shutdown = shutdown; +var child_process_1 = require("child_process"); +function shutdown() { + if (process.platform === 'win32') { + return shutdownWindows(); + } + else if (process.platform === 'linux') { + return shutdownLinux(); + } + return Promise.resolve({ + succeed: false, + message: 'Platform not supported', + }); +} +function shutdownWindows() { + return new Promise(function (resolve, reject) { + (0, child_process_1.exec)('shutdown /s /t 3', function (error, stdout, stderr) { + if (error) { + console.error("Error shutting down Windows: ".concat(error.message)); + return resolve({ succeed: false, message: error.message }); + } + if (stderr) { + console.error("Error shutting down Windows: ".concat(stderr)); + return resolve({ succeed: false, message: stderr }); + } + console.log("Windows shutdown command executed: ".concat(stdout)); + resolve({ succeed: true }); + }); + }); +} +function shutdownLinux() { + return new Promise(function (resolve, reject) { + (0, child_process_1.exec)('shutdown now', function (error, stdout, stderr) { + if (error) { + console.error("Error shutting down Linux: ".concat(error.message)); + return resolve({ succeed: false, message: error.message }); + } + if (stderr) { + console.error("Error shutting down Linux: ".concat(stderr)); + return resolve({ succeed: false, message: stderr }); + } + console.log("Linux shutdown command executed: ".concat(stdout)); + resolve({ succeed: true }); + }); + }); +} +//# sourceMappingURL=Shutdown.js.map \ No newline at end of file diff --git a/dist/Shutdown.js.map b/dist/Shutdown.js.map new file mode 100644 index 0000000..24cda5e --- /dev/null +++ b/dist/Shutdown.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Shutdown.js","sourceRoot":"","sources":["../src/Shutdown.ts"],"names":[],"mappings":";;AAEA,4BAWC;AAbD,+CAAqC;AAErC,SAAgB,QAAQ;IACvB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAClC,OAAO,eAAe,EAAE,CAAC;IAC1B,CAAC;SAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzC,OAAO,aAAa,EAAE,CAAC;IACxB,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,CAAC;QACtB,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,wBAAwB;KACjC,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe;IACvB,OAAO,IAAI,OAAO,CACjB,UAAC,OAAO,EAAE,MAAM;QACf,IAAA,oBAAI,EAAC,kBAAkB,EAAE,UAAC,KAAK,EAAE,MAAM,EAAE,MAAM;YAC9C,IAAI,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CACZ,uCAAgC,KAAK,CAAC,OAAO,CAAE,CAC/C,CAAC;gBACF,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,uCAAgC,MAAM,CAAE,CAAC,CAAC;gBACxD,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACrD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,6CAAsC,MAAM,CAAE,CAAC,CAAC;YAC5D,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACJ,CAAC,CACD,CAAC;AACH,CAAC;AAED,SAAS,aAAa;IACrB,OAAO,IAAI,OAAO,CACjB,UAAC,OAAO,EAAE,MAAM;QACf,IAAA,oBAAI,EAAC,cAAc,EAAE,UAAC,KAAK,EAAE,MAAM,EAAE,MAAM;YAC1C,IAAI,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CACZ,qCAA8B,KAAK,CAAC,OAAO,CAAE,CAC7C,CAAC;gBACF,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,qCAA8B,MAAM,CAAE,CAAC,CAAC;gBACtD,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACrD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,2CAAoC,MAAM,CAAE,CAAC,CAAC;YAC1D,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACJ,CAAC,CACD,CAAC;AACH,CAAC"} \ No newline at end of file diff --git a/dist/SocketServer.js b/dist/SocketServer.js new file mode 100644 index 0000000..7d784b3 --- /dev/null +++ b/dist/SocketServer.js @@ -0,0 +1,87 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SocketServer = void 0; +var socket_io_1 = require("socket.io"); +var Reboot_1 = require("./Reboot"); +var PREFIX = '[SocketServer]'; +var SocketServer = /** @class */ (function () { + function SocketServer(Main) { + this._Main = Main; + this.prepare(); + } + SocketServer.prototype.prepare = function () { + var _this = this; + this.socket = new socket_io_1.Server(); + this.socket.on('connection', function (socket) { + socket.on('getStatus', function (callback) { + if (typeof callback !== 'function') + return; + callback(_this._Main.ProcessWatcher.getStatus()); + }); + socket.on('restart', function (callback) { + if (typeof callback !== 'function') + return; + callback(_this._Main.ProcessWatcher.requestRestart()); + }); + socket.on('reboot', function (callback) { return __awaiter(_this, void 0, void 0, function () { + var _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (typeof callback !== 'function') + return [2 /*return*/]; + _a = callback; + return [4 /*yield*/, (0, Reboot_1.reboot)()]; + case 1: + _a.apply(void 0, [_b.sent()]); + return [2 /*return*/]; + } + }); + }); }); + }); + }; + SocketServer.prototype.listen = function () { + var port = this._Main.Config.socketServer.port; + this.socket.listen(port); + console.log(PREFIX, "Listening on port http://127.0.0.1:".concat(port)); + }; + return SocketServer; +}()); +exports.SocketServer = SocketServer; +//# sourceMappingURL=SocketServer.js.map \ No newline at end of file diff --git a/dist/SocketServer.js.map b/dist/SocketServer.js.map new file mode 100644 index 0000000..2622469 --- /dev/null +++ b/dist/SocketServer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"SocketServer.js","sourceRoot":"","sources":["../src/SocketServer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAA6D;AAE7D,mCAAkC;AAElC,IAAM,MAAM,GAAG,gBAAgB,CAAC;AAChC;IAKC,sBAAY,IAAU;QACrB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAElB,IAAI,CAAC,OAAO,EAAE,CAAC;IAChB,CAAC;IAEO,8BAAO,GAAf;QAAA,iBAsCC;QArCA,IAAI,CAAC,MAAM,GAAG,IAAI,kBAAc,EAAE,CAAC;QAEnC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,UAAC,MAAc;YAC3C,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,UAAC,QAAkB;gBACzC,IAAI,OAAO,QAAQ,KAAK,UAAU;oBAAE,OAAO;gBAE3C,QAAQ,CAAC,KAAI,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC,CAAC;YACjD,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CACR,SAAS,EACT,UACC,QAGU;gBAEV,IAAI,OAAO,QAAQ,KAAK,UAAU;oBAAE,OAAO;gBAE3C,QAAQ,CAAC,KAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,EAAE,CAAC,CAAC;YACtD,CAAC,CACD,CAAC;YAEF,MAAM,CAAC,EAAE,CACR,QAAQ,EACR,UACC,QAGU;;;;;4BAEV,IAAI,OAAO,QAAQ,KAAK,UAAU;gCAAE,sBAAO;4BAE3C,KAAA,QAAQ,CAAA;4BAAC,qBAAM,IAAA,eAAM,GAAE,EAAA;;4BAAvB,kBAAS,SAAc,EAAC,CAAC;;;;iBACzB,CACD,CAAC;QACH,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,6BAAM,GAAN;QACC,IAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC;QACjD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,6CAAsC,IAAI,CAAE,CAAC,CAAC;IACnE,CAAC;IACF,mBAAC;AAAD,CAAC,AAxDD,IAwDC;AAxDY,oCAAY"} \ No newline at end of file diff --git a/dist/SocketWerver.js b/dist/SocketWerver.js new file mode 100644 index 0000000..c727a38 --- /dev/null +++ b/dist/SocketWerver.js @@ -0,0 +1,22 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.WebServer = void 0; +var socket_io_1 = require("socket.io"); +var PREFIX = '[WebServer]'; +var WebServer = /** @class */ (function () { + function WebServer(Main) { + this._Main = Main; + this.prepare(); + } + WebServer.prototype.prepare = function () { + this.socket = new socket_io_1.Server(); + }; + WebServer.prototype.listen = function () { + var port = this._Main.Config.webServer.port; + this.socket.listen(port); + console.log(PREFIX, "Listening on port http://127.0.0.1:".concat(port)); + }; + return WebServer; +}()); +exports.WebServer = WebServer; +//# sourceMappingURL=SocketWerver.js.map \ No newline at end of file diff --git a/dist/SocketWerver.js.map b/dist/SocketWerver.js.map new file mode 100644 index 0000000..c0239bf --- /dev/null +++ b/dist/SocketWerver.js.map @@ -0,0 +1 @@ +{"version":3,"file":"SocketWerver.js","sourceRoot":"","sources":["../src/SocketWerver.ts"],"names":[],"mappings":";;;AAAA,uCAA6D;AAG7D,IAAM,MAAM,GAAG,aAAa,CAAC;AAC7B;IAKC,mBAAY,IAAU;QACrB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAElB,IAAI,CAAC,OAAO,EAAE,CAAC;IAChB,CAAC;IAEO,2BAAO,GAAf;QACC,IAAI,CAAC,MAAM,GAAG,IAAI,kBAAc,EAAE,CAAC;IACpC,CAAC;IAED,0BAAM,GAAN;QACC,IAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,6CAAsC,IAAI,CAAE,CAAC,CAAC;IACnE,CAAC;IACF,gBAAC;AAAD,CAAC,AApBD,IAoBC;AApBY,8BAAS"} \ No newline at end of file diff --git a/dist/Utils.js b/dist/Utils.js new file mode 100644 index 0000000..f995540 --- /dev/null +++ b/dist/Utils.js @@ -0,0 +1,7 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.delay = delay; +function delay(duration) { + return new Promise(function (resolve) { return setTimeout(resolve, duration); }); +} +//# sourceMappingURL=Utils.js.map \ No newline at end of file diff --git a/dist/Utils.js.map b/dist/Utils.js.map new file mode 100644 index 0000000..b598a96 --- /dev/null +++ b/dist/Utils.js.map @@ -0,0 +1 @@ +{"version":3,"file":"Utils.js","sourceRoot":"","sources":["../src/Utils.ts"],"names":[],"mappings":";;AAAA,sBAEC;AAFD,SAAgB,KAAK,CAAC,QAAgB;IACrC,OAAO,IAAI,OAAO,CAAC,UAAC,OAAO,IAAK,OAAA,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,EAA7B,CAA6B,CAAC,CAAC;AAChE,CAAC"} \ No newline at end of file diff --git a/dist/WebServer.js b/dist/WebServer.js new file mode 100644 index 0000000..6c18417 --- /dev/null +++ b/dist/WebServer.js @@ -0,0 +1,22 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.WebServer = void 0; +var socket_io_1 = require("socket.io"); +var PREFIX = '[WebServer]'; +var WebServer = /** @class */ (function () { + function WebServer(Main) { + this._Main = Main; + this.prepare(); + } + WebServer.prototype.prepare = function () { + this.socket = new socket_io_1.Server(); + }; + WebServer.prototype.listen = function () { + var port = this._Main.Config.webServer.port; + this.socket.listen(port); + console.log(PREFIX, "Listening on port http://127.0.0.1:".concat(port)); + }; + return WebServer; +}()); +exports.WebServer = WebServer; +//# sourceMappingURL=WebServer.js.map \ No newline at end of file diff --git a/dist/WebServer.js.map b/dist/WebServer.js.map new file mode 100644 index 0000000..8fbe855 --- /dev/null +++ b/dist/WebServer.js.map @@ -0,0 +1 @@ +{"version":3,"file":"WebServer.js","sourceRoot":"","sources":["../src/WebServer.ts"],"names":[],"mappings":";;;AAAA,uCAA6D;AAG7D,IAAM,MAAM,GAAG,aAAa,CAAC;AAC7B;IAKC,mBAAY,IAAU;QACrB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAElB,IAAI,CAAC,OAAO,EAAE,CAAC;IAChB,CAAC;IAEO,2BAAO,GAAf;QACC,IAAI,CAAC,MAAM,GAAG,IAAI,kBAAc,EAAE,CAAC;IACpC,CAAC;IAED,0BAAM,GAAN;QACC,IAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,6CAAsC,IAAI,CAAE,CAAC,CAAC;IACnE,CAAC;IACF,gBAAC;AAAD,CAAC,AApBD,IAoBC;AApBY,8BAAS"} \ No newline at end of file diff --git a/dist/processWatcher.js b/dist/processWatcher.js new file mode 100644 index 0000000..39bba04 --- /dev/null +++ b/dist/processWatcher.js @@ -0,0 +1,205 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); + return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ProcessWatcher = void 0; +var fs_extra_1 = require("fs-extra"); +var child_process_1 = require("child_process"); +var Utils_1 = require("./Utils"); +var PREFIX = '[ProcessWatcher]'; +var LOG_OUTPUT = !process.argv.includes('--no-output-log'); +var ProcessWatcher = /** @class */ (function () { + function ProcessWatcher(Main) { + this.state = 'STOPPED'; + this.startTime = null; + this.output = { current: [], last: [] }; + this.restartTriggered = false; + this._Main = Main; + } + ProcessWatcher.prototype.requestRestart = function () { + if (this.state !== 'RUNNING') + return { + succeed: false, + message: 'Cannot restart when process is not running. It is probably restarting already.', + }; + this.restart(true); + return { succeed: true }; + }; + ProcessWatcher.prototype.broadcastState = function () { + this._Main.SocketServer.socket.emit('simpleStatus', { + state: this.state, + message: this.message, + error: this.error, + }); + }; + ProcessWatcher.prototype.setInfo = function (message, error, state) { + if (state === void 0) { state = 'PROBLEM'; } + this.message = message; + this.error = error; + this.state = state; + this.broadcastState(); + this.output.current.push("[".concat(new Date().toLocaleTimeString('nl-NL'), "] [System] [").concat(state, "] ").concat(message !== null && message !== void 0 ? message : error)); + if (state == 'PROBLEM' || state == 'STOPPED') + console.warn(PREFIX, message !== null && message !== void 0 ? message : error); + else + console.log(PREFIX, message !== null && message !== void 0 ? message : error); + }; + ProcessWatcher.prototype.restart = function () { + return __awaiter(this, arguments, void 0, function (instant) { + if (instant === void 0) { instant = false; } + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + if (this.restartTriggered) + return [2 /*return*/]; + this.restartTriggered = true; + this.startTime = -1; + this.broadcastState(); + if (instant) + this.setInfo('Process will restart shortly...', null, 'STOPPED'); + if (!(this.process != null)) return [3 /*break*/, 2]; + this.process.kill('SIGTERM'); + return [4 /*yield*/, (0, Utils_1.delay)(3000)]; + case 1: + _a.sent(); + if (!this.process.killed && this.process.exitCode === null) { + this.process.kill('SIGKILL'); + console.log(PREFIX, 'Sent SIGKILL to process.'); + } + _a.label = 2; + case 2: + this.startTime = -1; + if (!!instant) return [3 /*break*/, 4]; + this.setInfo("Restarting process in 5 seconds...", null, 'STOPPED'); + return [4 /*yield*/, (0, Utils_1.delay)(5000)]; + case 3: + _a.sent(); + _a.label = 4; + case 4: return [4 /*yield*/, this.start()]; + case 5: + _a.sent(); + return [2 /*return*/]; + } + }); + }); + }; + ProcessWatcher.prototype.start = function () { + var _this = this; + var _a, _b; + this.output.last = __spreadArray([], this.output.current, true); + this.output.current = []; + this.startTime = Date.now(); + this.restartTriggered = false; + this.broadcastState(); + var path = this._Main.Config.executable.path; + if (path == null || !(0, fs_extra_1.pathExistsSync)(path)) { + this.setInfo('Executable problem', "Executable path is not set or does not exist: ".concat(path)); + return; + } + var fileName = path.split(/(\/|\\)/).pop(); + this.setInfo("Starting executable: ".concat(fileName), null, 'STARTING'); + this.process = (0, child_process_1.spawn)(path, this._Main.Config.executable.arguments, { + stdio: 'pipe', + }); + this.process.on('exit', function (code, signal) { + if (_this.restartTriggered) + return; + _this.setInfo('Process exited', "Process exited with code ".concat(code, " and signal ").concat(signal), 'STOPPED'); + _this.restart(); + }); + this.process.on('error', function (err) { + _this.setInfo('Process error', err.message); + _this.restart(); + }); + (_a = this.process.stdout) === null || _a === void 0 ? void 0 : _a.on('data', function (data) { + var lines = data + .toString() + .trim() + .split('\n') + .filter(function (line) { return line.length > 0; }); + lines.forEach(function (line) { + var formattedLine = "[".concat(new Date().toLocaleTimeString('nl-NL'), "] [").concat(fileName, "] ").concat(line); + if (LOG_OUTPUT) + console.log(PREFIX, formattedLine); + _this.output.current.push(formattedLine); + }); + }); + (_b = this.process.stderr) === null || _b === void 0 ? void 0 : _b.on('data', function (data) { + var lines = data + .toString() + .trim() + .split('\n') + .filter(function (line) { return line.length > 0; }); + lines.forEach(function (line) { + var formattedLine = "[".concat(new Date().toLocaleTimeString('nl-NL'), "] [").concat(fileName, "] [ERROR] ").concat(line); + if (LOG_OUTPUT) + console.error(PREFIX, formattedLine); + _this.output.current.push(formattedLine); + }); + }); + setTimeout(function () { + var _a, _b; + if (_this.process == null || + ((_a = _this.process) === null || _a === void 0 ? void 0 : _a.killed) || + ((_b = _this.process) === null || _b === void 0 ? void 0 : _b.exitCode) != null || + _this.restartTriggered) + return; + _this.setInfo('Running', '', 'RUNNING'); + }, 5000); + }; + ProcessWatcher.prototype.getStatus = function () { + return { + state: this.state, + message: this.message, + error: this.error, + startTime: this.startTime, + output: this.output, + }; + }; + return ProcessWatcher; +}()); +exports.ProcessWatcher = ProcessWatcher; +//# sourceMappingURL=processWatcher.js.map \ No newline at end of file diff --git a/dist/processWatcher.js.map b/dist/processWatcher.js.map new file mode 100644 index 0000000..cc8651a --- /dev/null +++ b/dist/processWatcher.js.map @@ -0,0 +1 @@ +{"version":3,"file":"processWatcher.js","sourceRoot":"","sources":["../src/processWatcher.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..045bd62 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,315 @@ +{ + "name": "ntshcamerarunner", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ntshcamerarunner", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@types/fs-extra": "^11.0.4", + "@types/node": "^24.9.1", + "fs-extra": "^11.3.2", + "socket.io": "^4.8.1" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "license": "MIT", + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "24.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", + "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..9a82c86 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "ntshcamerarunner", + "version": "1.0.0", + "main": "dist/Main.js", + "scripts": { + "start": "node ." + }, + "author": "Mees van der Wijk - Morphix Productions", + "license": "ISC", + "description": "", + "dependencies": { + "@types/fs-extra": "^11.0.4", + "@types/node": "^24.9.1", + "fs-extra": "^11.3.2", + "socket.io": "^4.8.1" + } +} \ No newline at end of file diff --git a/src/Configuration/ConfigurationManager.ts b/src/Configuration/ConfigurationManager.ts new file mode 100644 index 0000000..bd2c341 --- /dev/null +++ b/src/Configuration/ConfigurationManager.ts @@ -0,0 +1,113 @@ +import { ensureDir, pathExists, readJSON, writeFile } from 'fs-extra'; +import { Main } from '../Main'; +import { join } from 'path'; +import { DefaultConfiguration } from './DefaultConfiguration'; + +const PREFIX = '[ConfigurationManager]'; +export class ConfigurationManager { + private _Main: Main; + + constructor(Main: Main) { + this._Main = Main; + } + + async load() { + console.log(PREFIX, 'Loading'); + + await ensureDir(join(this._Main.dataPath)); + + let configPath = join(this._Main.dataPath, 'config.json'); + let configExists = await pathExists(configPath); + if (!configExists) { + await writeFile( + configPath, + JSON.stringify(DefaultConfiguration, null, 4) + ); + this._Main.Config = DefaultConfiguration; + console.log(PREFIX, 'Written default configuration'); + } else { + var config: Config = await readJSON(configPath); + + this._Main.Config = await this.validateConfig(config); + console.log(PREFIX, 'Loaded configuration'); + } + } + + async validateConfig(config: Config): Promise { + const normalizedConfig: Config = this.normalizeConfig( + config, + DefaultConfiguration + ); + const hasChanges = + JSON.stringify(config) !== JSON.stringify(normalizedConfig); + + if (hasChanges) { + this._Main.Config = normalizedConfig; + + const configPath = join(this._Main.dataPath, 'config.json'); + await writeFile( + configPath, + JSON.stringify(normalizedConfig, null, 4) + ); + console.log(PREFIX, 'Configuration updated and saved'); + } + + return normalizedConfig; + } + + private normalizeConfig(current: any, template: any): any { + if (template === null || template === undefined) { + return template; + } + + if (typeof template !== 'object' || Array.isArray(template)) { + if ( + current !== null && + current !== undefined && + typeof current === typeof template + ) { + if (Array.isArray(template) && Array.isArray(current)) { + if ( + template.length > 0 && + typeof template[0] === 'object' && + !Array.isArray(template[0]) + ) { + return current.map((item) => + this.normalizeConfig(item, template[0]) + ); + } + return current; + } + return current; + } + return template; + } + + const result: any = {}; + + for (const key in template) { + if (template.hasOwnProperty(key)) { + result[key] = this.normalizeConfig( + current?.[key], + template[key] + ); + } + } + + return result; + } +} + +export interface Config { + socketServer: ConfigSocketServer; + executable: ConfigExecutable; +} + +interface ConfigSocketServer { + port: number; +} + +interface ConfigExecutable { + path: string; + arguments: string[]; +} diff --git a/src/Configuration/DefaultConfiguration.ts b/src/Configuration/DefaultConfiguration.ts new file mode 100644 index 0000000..83aec6e --- /dev/null +++ b/src/Configuration/DefaultConfiguration.ts @@ -0,0 +1,11 @@ +import { Config } from './ConfigurationManager'; + +export const DefaultConfiguration: Config = { + socketServer: { + port: 6301, + }, + executable: { + path: '', + arguments: [], + }, +}; diff --git a/src/Main.ts b/src/Main.ts new file mode 100644 index 0000000..4c85538 --- /dev/null +++ b/src/Main.ts @@ -0,0 +1,36 @@ +import { join } from 'path'; +import { + Config, + ConfigurationManager, +} from './Configuration/ConfigurationManager'; +import { homedir } from 'os'; +import { SocketServer } from './SocketServer'; +import { ProcessWatcher } from './processWatcher'; +import { delay } from './Utils'; + +export class Main { + dataPath = join( + homedir(), + 'MorphixProductions', + 'NTSHControl', + 'CameraRunner' + ); + + ConfigurationManager = new ConfigurationManager(this); + SocketServer = new SocketServer(this); + ProcessWatcher = new ProcessWatcher(this); + + Config: Config; + + async start() { + await this.ConfigurationManager.load(); + await this.SocketServer.listen(); + + await delay(5000); + + await this.ProcessWatcher.start(); + } +} + +const _Main = new Main(); +_Main.start(); diff --git a/src/Reboot.ts b/src/Reboot.ts new file mode 100644 index 0000000..b568b8c --- /dev/null +++ b/src/Reboot.ts @@ -0,0 +1,56 @@ +import { exec } from 'child_process'; + +export function reboot(): Promise<{ succeed: boolean; message?: string }> { + if (process.platform === 'win32') { + return rebootWindows(); + } else if (process.platform === 'linux') { + return rebootLinux(); + } + + return Promise.resolve({ + succeed: false, + message: 'Platform not supported', + }); +} + +function rebootWindows(): Promise<{ succeed: boolean; message?: string }> { + return new Promise<{ succeed: boolean; message?: string }>( + (resolve, reject) => { + exec('shutdown /r /t 3', (error, stdout, stderr) => { + if (error) { + console.error( + `Error shutting down Windows: ${error.message}` + ); + return resolve({ succeed: false, message: error.message }); + } + if (stderr) { + console.error(`Error shutting down Windows: ${stderr}`); + return resolve({ succeed: false, message: stderr }); + } + console.log(`Windows shutdown command executed: ${stdout}`); + resolve({ succeed: true }); + }); + } + ); +} + +function rebootLinux(): Promise<{ succeed: boolean; message?: string }> { + return new Promise<{ succeed: boolean; message?: string }>( + (resolve, reject) => { + exec('shutdown -r now', (error, stdout, stderr) => { + if (error) { + console.error( + `Error shutting down Linux: ${error.message}` + ); + return resolve({ succeed: false, message: error.message }); + } + if (stderr) { + console.error(`Error shutting down Linux: ${stderr}`); + return resolve({ succeed: false, message: stderr }); + } + console.log(`Linux shutdown command executed: ${stdout}`); + resolve({ succeed: true }); + }); + } + ); +} diff --git a/src/SocketServer.ts b/src/SocketServer.ts new file mode 100644 index 0000000..256def0 --- /dev/null +++ b/src/SocketServer.ts @@ -0,0 +1,62 @@ +import { Socket, Server as SocketIOServer } from 'socket.io'; +import { Main } from './Main'; +import { reboot } from './Reboot'; + +const PREFIX = '[SocketServer]'; +export class SocketServer { + private _Main: Main; + + socket: SocketIOServer; + + constructor(Main: Main) { + this._Main = Main; + + this.prepare(); + } + + private prepare() { + this.socket = new SocketIOServer(); + + this.socket.on('connection', (socket: Socket) => { + socket.on('getStatus', (callback: Function) => { + if (typeof callback !== 'function') return; + + callback(this._Main.ProcessWatcher.getStatus()); + }); + + socket.on( + 'restart', + ( + callback: (response: { + succeed: boolean; + message?: string; + }) => void + ) => { + if (typeof callback !== 'function') return; + + callback(this._Main.ProcessWatcher.requestRestart()); + } + ); + + socket.on( + 'reboot', + async ( + callback: (response: { + succeed: boolean; + message?: string; + }) => void + ) => { + if (typeof callback !== 'function') return; + + callback(await reboot()); + } + ); + }); + } + + listen() { + const port = this._Main.Config.socketServer.port; + this.socket.listen(port); + console.log(PREFIX, `Listening on port http://127.0.0.1:${port}`); + } +} diff --git a/src/Utils.ts b/src/Utils.ts new file mode 100644 index 0000000..17aadc3 --- /dev/null +++ b/src/Utils.ts @@ -0,0 +1,3 @@ +export function delay(duration: number): Promise { + return new Promise((resolve) => setTimeout(resolve, duration)); +} diff --git a/src/processWatcher.ts b/src/processWatcher.ts new file mode 100644 index 0000000..d4882d9 --- /dev/null +++ b/src/processWatcher.ts @@ -0,0 +1,185 @@ +import { pathExistsSync } from 'fs-extra'; +import { Main } from './Main'; +import { ChildProcess, exec, spawn } from 'child_process'; +import { delay } from './Utils'; + +const PREFIX = '[ProcessWatcher]'; + +const LOG_OUTPUT = !process.argv.includes('--no-output-log'); + +export class ProcessWatcher { + private _Main: Main; + + state: CameraRunnerState = 'STOPPED'; + message?: string; + error?: string; + + startTime: number = null; + output: { current: string[]; last: string[] } = { current: [], last: [] }; + + process: ChildProcess; + + constructor(Main: Main) { + this._Main = Main; + } + + requestRestart(): { succeed: boolean; message?: string } { + if (this.state !== 'RUNNING') + return { + succeed: false, + message: + 'Cannot restart when process is not running. It is probably restarting already.', + }; + + this.restart(true); + return { succeed: true }; + } + + broadcastState() { + this._Main.SocketServer.socket.emit('simpleStatus', { + state: this.state, + message: this.message, + error: this.error, + }); + } + + setInfo( + message: string, + error: string, + state: CameraRunnerState = 'PROBLEM' + ) { + this.message = message; + this.error = error; + this.state = state; + this.broadcastState(); + + this.output.current.push( + `[${new Date().toLocaleTimeString('nl-NL')}] [System] [${state}] ${ + message ?? error + }` + ); + + if (state == 'PROBLEM' || state == 'STOPPED') + console.warn(PREFIX, message ?? error); + else console.log(PREFIX, message ?? error); + } + + private restartTriggered: boolean = false; + async restart(instant: boolean = false) { + if (this.restartTriggered) return; + this.restartTriggered = true; + this.startTime = -1; + this.broadcastState(); + + if (instant) + this.setInfo('Process will restart shortly...', null, 'STOPPED'); + + if (this.process != null) { + this.process.kill('SIGTERM'); + + await delay(3000); + + if (!this.process.killed && this.process.exitCode === null) { + this.process.kill('SIGKILL'); + console.log(PREFIX, 'Sent SIGKILL to process.'); + } + } + this.startTime = -1; + + if (!instant) { + this.setInfo(`Restarting process in 5 seconds...`, null, 'STOPPED'); + await delay(5000); + } + await this.start(); + } + + start() { + this.output.last = [...this.output.current]; + this.output.current = []; + this.startTime = Date.now(); + this.restartTriggered = false; + this.broadcastState(); + + const path = this._Main.Config.executable.path; + if (path == null || !pathExistsSync(path)) { + this.setInfo( + 'Executable problem', + `Executable path is not set or does not exist: ${path}` + ); + return; + } + + const fileName = path.split(/(\/|\\)/).pop(); + + this.setInfo(`Starting executable: ${fileName}`, null, 'STARTING'); + + this.process = spawn(path, this._Main.Config.executable.arguments, { + stdio: 'pipe', + }); + + this.process.on('exit', (code, signal) => { + if (this.restartTriggered) return; + this.setInfo( + 'Process exited', + `Process exited with code ${code} and signal ${signal}`, + 'STOPPED' + ); + this.restart(); + }); + this.process.on('error', (err) => { + this.setInfo('Process error', err.message); + this.restart(); + }); + this.process.stdout?.on('data', (data) => { + const lines = data + .toString() + .trim() + .split('\n') + .filter((line) => line.length > 0); + lines.forEach((line) => { + const formattedLine = `[${new Date().toLocaleTimeString( + 'nl-NL' + )}] [${fileName}] ${line}`; + if (LOG_OUTPUT) console.log(PREFIX, formattedLine); + this.output.current.push(formattedLine); + }); + }); + this.process.stderr?.on('data', (data) => { + const lines = data + .toString() + .trim() + .split('\n') + .filter((line) => line.length > 0); + lines.forEach((line) => { + const formattedLine = `[${new Date().toLocaleTimeString( + 'nl-NL' + )}] [${fileName}] [ERROR] ${line}`; + if (LOG_OUTPUT) console.error(PREFIX, formattedLine); + this.output.current.push(formattedLine); + }); + }); + setTimeout(() => { + if ( + this.process == null || + this.process?.killed || + this.process?.exitCode != null || + this.restartTriggered + ) + return; + this.setInfo('Running', '', 'RUNNING'); + }, 5000); + } + + getStatus() { + return { + state: this.state, + message: this.message, + error: this.error, + + startTime: this.startTime, + output: this.output, + }; + } +} + +export type CameraRunnerState = 'RUNNING' | 'STOPPED' | 'STARTING' | 'PROBLEM'; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ffb5ccb --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "outDir": "dist", + "sourceMap": true + }, + "include": [ + "src/**/*" + ], +} \ No newline at end of file