control-freak-ide/server/nodejs/_build/services/external/Mongod.js
plastic-hub-dev-node-saturn 538369cff7 latest
2021-05-12 18:35:18 +02:00

463 lines
17 KiB
JavaScript

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const External_1 = require("../External");
const mkdirp = require("mkdirp");
const console_1 = require("../../console");
const os = require('os');
const arch = os.arch();
const path = require('path');
const fs = require('fs');
const jet = require('fs-jetpack');
const childprocess = require('child_process');
let Registry = require('winreg');
function isOSWin64() {
return process.arch === 'x64' || process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
}
function exists(_path) {
try {
return fs.statSync(_path);
}
catch (e) {
}
return null;
}
var EError;
(function (EError) {
EError[EError["WAITING"] = 1] = "WAITING";
EError[EError["ALREADY_IN_USE"] = 2] = "ALREADY_IN_USE";
EError[EError["DENIED"] = 3] = "DENIED";
EError[EError["ERROR"] = 4] = "ERROR";
})(EError = exports.EError || (exports.EError = {}));
/**
* {@link Mongod.parseData} to
* parse stdout and stderr messages.
* @see Mongod.parseData
* @readonly
* @private
* @type {Object.<String,RegExp>}
*/
const MessageRegEx = {
terminalMessage: /waiting\s+for\s+connections|already\s+in\s+use|denied|error|exception|badvalue/im,
whiteSpace: /\s/g,
newline: /\r?\n/
};
class Mongod extends External_1.ExternalService {
constructor(config, searchPaths, debug = false) {
super(null);
this.promiseQueue = null;
this.openPromise = null;
this.debug = false;
this.isRunning = false;
this.isClosing = false;
this.isOpening = false;
this.flags = External_1.EFlags.REQUIRED | External_1.EFlags.SHARED;
this.mongoConfig = config;
this.debug = debug;
this.searchPaths = searchPaths || [];
}
//
// ─── IMPLEMENT EXTERNAL SERVICE ──────────────────────────────────────────────────
//
filename() {
return 'mongod' + (os.platform() === 'win32' ? '.exe' : '');
}
label() {
return 'MongoDB';
}
stop() {
return __awaiter(this, void 0, void 0, function* () {
if (this.isClosing) {
return this.closePromise;
}
this.isClosing = true;
this.isOpening = false;
this.closePromise = new Promise((resolve) => {
if (this.isOpening || !this.isRunning) {
this.isClosing = false;
return Promise.resolve(null);
}
this.process.once('close', () => resolve(null));
this.process.kill();
});
return this.closePromise;
});
}
start() {
return __awaiter(this, void 0, void 0, function* () {
const exe = this.find();
if (!exe) {
Promise.reject("Cant find mongod binary!");
return false;
}
this.status = External_1.EStatus.STARTING;
if (this.isClosing || this.isRunning) {
this.isOpening = false;
return Promise.resolve(null);
}
return new Promise((resolve, reject) => {
/**
* A listener for the current server process' stdout/stderr that
* resolves or rejects the current {@link Promise} when done.
* @see Mongod.getTextLineAggregator
* @see Mongod.parseData
* @argument {Buffer} buffer
* @return {undefined}
*/
const dataListener = Mongod.getTextLineAggregator((value) => {
const result = Mongod.parseData(value);
if (result === null) {
return;
}
this.process.stdout.removeListener('data', dataListener);
this.isOpening = false;
if (result.err === null) {
this.isRunning = true;
resolve(null);
}
else {
switch (result.err.code) {
case EError.ALREADY_IN_USE: {
if (!(this.flags & External_1.EFlags.SHARED)) {
this.isClosing = true;
}
else {
this.isRunning = true;
resolve(null);
}
break;
}
case EError.DENIED:
case EError.ERROR: {
this.isClosing = true;
reject(result.err.message);
}
}
this.process.once('close', () => reject(result.err.message));
}
});
/**
* A listener to close the server when the current process exits.
* @return {undefined}
*/
const exitListener = () => {
// istanbul ignore next
this.stop();
};
/**
* Get a text line aggregator that emits a given {@linkcode event} for the current server.
* @see Mongod.getTextLineAggregator
* @argument {String} event
* @return {Function}
*/
const getDataPropagator = (event) => Mongod.getTextLineAggregator((line) => function (event, data) { });
console_1.console.info('start Mongo ' + this.mongoConfig.path + ' at port ' + this.mongoConfig.port + ' and data at ' + this.mongoConfig.db);
this.process = childprocess.spawn(this.mongoConfig.path, Mongod.parseFlags(this.mongoConfig));
this.process.stderr.on('data', dataListener);
this.process.stderr.on('data', getDataPropagator('stdout'));
this.process.stdout.on('data', dataListener);
this.process.stdout.on('data', getDataPropagator('stdout'));
this.process.on('close', () => {
this.process = null;
this.isRunning = false;
this.isClosing = false;
process.removeListener('exit', exitListener);
});
process.on('exit', exitListener);
});
});
}
;
canReadAndWrite(targetPath) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
fs.stat(targetPath, (err) => {
if (err) {
reject(err);
return;
}
fs.access(targetPath, fs.W_OK | fs.R_OK | fs.R_OK, (err) => {
if (err) {
const dir = path.dirname(targetPath);
fs.access(dir, fs.W_OK | fs.R_OK, (err) => {
if (err) {
reject(err);
return;
}
resolve(false);
});
}
resolve(true);
});
});
});
});
}
info() {
return {
label: this.label()
};
}
_tryProgramFiles() {
return __awaiter(this, void 0, void 0, function* () {
const programFiles = yield this._getProgramFilesDirectory();
const mongoInProgramFiles = programFiles + path.sep + 'MongoDB' + path.sep + 'Server';
return new Promise((resolve, reject) => {
if (exists(mongoInProgramFiles)) {
// we should have mongoInProgramFiles now at C:\Program Files\MongoDB\Server
// next step is to find the highest version possible :
let list = fs.readdirSync(mongoInProgramFiles);
let last = 0;
list.forEach(function (file) {
const v = parseFloat(file);
if (v > last) {
last = v;
}
});
if (last) {
const bin = mongoInProgramFiles + path.sep + last + path.sep + 'bin' + path.sep + 'mongod.exe';
if (exists(bin)) {
resolve(bin);
}
else {
reject('Cant find ' + bin);
}
}
else {
reject('Cant find ' + mongoInProgramFiles);
}
}
else {
reject('false');
}
});
});
}
_getProgramFilesDirectory() {
return __awaiter(this, void 0, void 0, function* () {
const is64 = isOSWin64();
const key = new Registry({
hive: Registry.HKLM,
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\'
});
const loc = is64 ? 'ProgramW6432Dir' : 'ProgramFiles';
return new Promise((resolve, reject) => {
key.get(loc, function (err, item) {
if (err) {
reject(err);
}
if (item) {
resolve(item.value);
}
});
});
});
}
// override init to adjust default search paths since we're deploying mongod together with the applications
init() {
return __awaiter(this, void 0, void 0, function* () {
switch (os.platform()) {
case 'win32': {
try {
const mongoInProgramFiles = yield this._tryProgramFiles();
if (mongoInProgramFiles) {
this.searchPaths = this.searchPaths.concat([mongoInProgramFiles]);
}
}
catch (e) {
}
//in export scenario, mongod exists in root/mongo/mongod-[platform].exe
const found = [];
this.searchPaths.forEach(_path => {
const bin = _path + path.sep + 'mongod-windows.exe';
if (exists(bin)) {
found.push(bin);
}
});
this.searchPaths.push(...found);
break;
}
case 'darwin':
case 'linux': {
this.searchPaths = this.searchPaths.concat(['/usr/bin/', '/usr/local/bin/']);
break;
}
}
const config = this.mongoConfig;
this.mongoConfig = Mongod.parseConfig(config, {
path: config.path ? config.path : this.find(),
config: null,
port: 27017,
db: null,
engine: 'mmapv1',
nojournal: true,
smallFiles: true,
quite: true
});
yield this.canReadAndWrite(this.mongoConfig.db);
mkdirp(this.mongoConfig.db, (err, made) => {
if (err) {
Promise.reject("Cant create MongoDB data base path : " + this.mongoConfig.db);
}
else {
Promise.resolve(true);
}
});
jet.remove(path.join(this.mongoConfig.db, 'mongod.lock'));
return Promise.resolve(true);
});
}
//
// ─── UTILS ──────────────────────────────────────────────────────────────────────
//
/*
* Parse process flags for MongoDB from a given {@link IMongoConfig}.
* @protected
* @argument {IMongoConfig} config
* @return {Array.<String>}
*/
static parseFlags(config) {
if (config.config != null) {
return ['--config', config.config];
}
const flags = [];
if (config.nojournal) {
flags.push('--nojournal');
}
if (config.smallfiles) {
flags.push('--smallfiles');
}
if (config.engine != null) {
if (!~arch.indexOf('64')) {
flags.push('--storageEngine', config.engine);
}
}
if (config.db != null) {
flags.push('--dbpath', config.db);
}
if (config.port != null) {
flags.push('--port', config.port);
}
if (config.quite === true) {
flags.push('--quiet');
}
return flags;
}
/*
* Populate a given {@link IMongoConfig} with values from a given {@link IMongoConfig}.
* @protected
* @argument {IMongoConfig} source
* @argument {IMongoConfig} target
* @return {IMongoConfig}
*/
static parseConfig(source, target) {
if (target == null) {
target = Object.create(null);
}
if (source == null) {
return target;
}
if (typeof source === 'number' || typeof source === 'string') {
target.port = source;
return target;
}
if (typeof source !== 'object') {
return target;
}
if (source.path != null) {
target.path = source.path;
}
if (source.config != null) {
target.config = source.config;
return target;
}
if (source.nojournal === true) {
target.nojournal = true;
}
if (source.engine != null) {
target.engine = source.engine;
}
if (source.db != null) {
target.db = source.db;
}
if (source.port != null) {
target.port = source.port;
}
if (source.quite != null) {
target.quite = source.quite;
}
return target;
}
/*
* Parse MongoDB server output for terminal messages.
* @protected
* @argument {String} string
* @return {Object}
*/
static parseData(str) {
const matches = MessageRegEx.terminalMessage.exec(str);
if (matches === null) {
return null;
}
const result = {
err: null,
key: matches
.pop()
.replace(MessageRegEx.whiteSpace, '')
.toLowerCase()
};
if (~str.indexOf('already in use')) {
result.err = new Error('Address already in use');
result.err.code = EError.ALREADY_IN_USE;
return result;
}
if (~str.indexOf('initialize Performance Counters for FTDC')) {
return null;
}
switch (result.key) {
case 'waitingforconnections':
break;
case 'alreadyinuse':
result.err = new Error('Address already in use');
result.err.code = EError.ALREADY_IN_USE;
break;
case 'denied':
result.err = new Error('Permission denied');
result.err.code = EError.DENIED;
break;
case 'error':
case 'exception':
case 'badvalue':
result.err = new Error(str.trim());
result.err.code = EError.ERROR;
break;
}
return result;
}
static getTextLineAggregator(callback) {
let buffer = '';
return (data) => {
const fragments = data.toString().split(MessageRegEx.newline);
const lines = fragments.slice(0, fragments.length - 1);
// If there was an unended line in the previous dump, complete it by
// the first section.
lines[0] = buffer + lines[0];
// If there is an unended line in this dump, store it to be completed by
// the next. This assumes there will be a terminating newline character
// at some point. Generally, this is a safe assumption.
buffer = fragments[fragments.length - 1];
for (let line of lines) {
callback(line);
}
};
}
}
exports.Mongod = Mongod;
//# sourceMappingURL=Mongod.js.map