electron-compile
Version:
Electron supporting package to compile JS and CSS in Electron applications
584 lines (466 loc) • 43.9 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _regenerator = require('babel-runtime/regenerator');
var _regenerator2 = _interopRequireDefault(_regenerator);
var _stringify = require('babel-runtime/core-js/json/stringify');
var _stringify2 = _interopRequireDefault(_stringify);
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _zlib = require('zlib');
var _zlib2 = _interopRequireDefault(_zlib);
var _crypto = require('crypto');
var _crypto2 = _interopRequireDefault(_crypto);
var _promise = require('./promise');
var _lodash = require('lodash');
var _lodash2 = _interopRequireDefault(_lodash);
var _sanitizePaths = require('./sanitize-paths');
var _sanitizePaths2 = _interopRequireDefault(_sanitizePaths);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var d = require('debug')('electron-compile:file-change-cache');
/**
* This class caches information about files and determines whether they have
* changed contents or not. Most importantly, this class caches the hash of seen
* files so that at development time, we don't have to recalculate them constantly.
*
* This class is also the core of how electron-compile runs quickly in production
* mode - after precompilation, the cache is serialized along with the rest of the
* data in {@link CompilerHost}, so that when we load the app in production mode,
* we don't end up calculating hashes of file content at all, only using the contents
* of this cache.
*/
var FileChangedCache = function () {
function FileChangedCache(appRoot) {
var failOnCacheMiss = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
(0, _classCallCheck3.default)(this, FileChangedCache);
this.appRoot = (0, _sanitizePaths2.default)(appRoot);
this.failOnCacheMiss = failOnCacheMiss;
this.changeCache = {};
}
/**
* Allows you to create a FileChangedCache from serialized data saved from
* {@link getSavedData}.
*
* @param {Object} data Saved data from getSavedData.
*
* @param {string} appRoot The top-level directory for your application (i.e.
* the one which has your package.json).
*
* @param {boolean} failOnCacheMiss (optional) If True, cache misses will throw.
*
* @return {FileChangedCache}
*/
(0, _createClass3.default)(FileChangedCache, [{
key: 'getHashForPath',
/**
* Returns information about a given file, including its hash. This method is
* the main method for this cache.
*
* @param {string} absoluteFilePath The path to a file to retrieve info on.
*
* @return {Promise<Object>}
*
* @property {string} hash The SHA1 hash of the file
* @property {boolean} isMinified True if the file is minified
* @property {boolean} isInNodeModules True if the file is in a library directory
* @property {boolean} hasSourceMap True if the file has a source map
* @property {boolean} isFileBinary True if the file is not a text file
* @property {Buffer} binaryData (optional) The buffer that was read if the file
* was binary and there was a cache miss.
* @property {string} code (optional) The string that was read if the file
* was text and there was a cache miss
*/
value: function () {
var ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(absoluteFilePath) {
var cacheKey, cacheEntry, stat, ctime, size, _ref, digest, sourceCode, binaryData, info;
return _regenerator2.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
cacheKey = (0, _sanitizePaths2.default)(absoluteFilePath);
if (this.appRoot) {
cacheKey = cacheKey.replace(this.appRoot, '');
}
// NB: We do this because x-require will include an absolute path from the
// original built app and we need to still grok it
if (this.originalAppRoot) {
cacheKey = cacheKey.replace(this.originalAppRoot, '');
}
cacheEntry = this.changeCache[cacheKey];
if (!this.failOnCacheMiss) {
_context.next = 10;
break;
}
if (cacheEntry) {
_context.next = 9;
break;
}
d('Tried to read file cache entry for ' + absoluteFilePath);
d('cacheKey: ' + cacheKey + ', appRoot: ' + this.appRoot + ', originalAppRoot: ' + this.originalAppRoot);
throw new Error('Asked for ' + absoluteFilePath + ' but it was not precompiled!');
case 9:
return _context.abrupt('return', cacheEntry.info);
case 10:
_context.next = 12;
return _promise.pfs.stat(absoluteFilePath);
case 12:
stat = _context.sent;
ctime = stat.ctime.getTime();
size = stat.size;
if (!(!stat || !stat.isFile())) {
_context.next = 17;
break;
}
throw new Error('Can\'t stat ' + absoluteFilePath);
case 17:
if (!cacheEntry) {
_context.next = 22;
break;
}
if (!(cacheEntry.ctime >= ctime && cacheEntry.size === size)) {
_context.next = 20;
break;
}
return _context.abrupt('return', cacheEntry.info);
case 20:
d('Invalidating cache entry: ' + cacheEntry.ctime + ' === ' + ctime + ' && ' + cacheEntry.size + ' === ' + size);
delete this.changeCache.cacheEntry;
case 22:
_context.next = 24;
return this.calculateHashForFile(absoluteFilePath);
case 24:
_ref = _context.sent;
digest = _ref.digest;
sourceCode = _ref.sourceCode;
binaryData = _ref.binaryData;
info = {
hash: digest,
isMinified: FileChangedCache.contentsAreMinified(sourceCode || ''),
isInNodeModules: FileChangedCache.isInNodeModules(absoluteFilePath),
hasSourceMap: FileChangedCache.hasSourceMap(sourceCode || ''),
isFileBinary: !!binaryData
};
this.changeCache[cacheKey] = { ctime: ctime, size: size, info: info };
d('Cache entry for ' + cacheKey + ': ' + (0, _stringify2.default)(this.changeCache[cacheKey]));
if (!binaryData) {
_context.next = 35;
break;
}
return _context.abrupt('return', _lodash2.default.extend({ binaryData: binaryData }, info));
case 35:
return _context.abrupt('return', _lodash2.default.extend({ sourceCode: sourceCode }, info));
case 36:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
return function getHashForPath(_x2) {
return ref.apply(this, arguments);
};
}()
/**
* Returns data that can passed to {@link loadFromData} to rehydrate this cache.
*
* @return {Object}
*/
}, {
key: 'getSavedData',
value: function getSavedData() {
return { changeCache: this.changeCache, appRoot: this.appRoot };
}
/**
* Serializes this object's data to a file.
*
* @param {string} filePath The path to save data to.
*
* @return {Promise} Completion.
*/
}, {
key: 'save',
value: function () {
var ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(filePath) {
var toSave, buf;
return _regenerator2.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
toSave = this.getSavedData();
_context2.next = 3;
return _promise.pzlib.gzip(new Buffer((0, _stringify2.default)(toSave)));
case 3:
buf = _context2.sent;
_context2.next = 6;
return _promise.pfs.writeFile(filePath, buf);
case 6:
case 'end':
return _context2.stop();
}
}
}, _callee2, this);
}));
return function save(_x3) {
return ref.apply(this, arguments);
};
}()
}, {
key: 'calculateHashForFile',
value: function () {
var ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(absoluteFilePath) {
var buf, encoding, _digest, sourceCode, digest;
return _regenerator2.default.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
_context3.next = 2;
return _promise.pfs.readFile(absoluteFilePath);
case 2:
buf = _context3.sent;
encoding = FileChangedCache.detectFileEncoding(buf);
if (encoding) {
_context3.next = 7;
break;
}
_digest = _crypto2.default.createHash('sha1').update(buf).digest('hex');
return _context3.abrupt('return', { sourceCode: null, digest: _digest, binaryData: buf });
case 7:
_context3.next = 9;
return _promise.pfs.readFile(absoluteFilePath, encoding);
case 9:
sourceCode = _context3.sent;
digest = _crypto2.default.createHash('sha1').update(sourceCode, 'utf8').digest('hex');
return _context3.abrupt('return', { sourceCode: sourceCode, digest: digest, binaryData: null });
case 12:
case 'end':
return _context3.stop();
}
}
}, _callee3, this);
}));
return function calculateHashForFile(_x4) {
return ref.apply(this, arguments);
};
}()
}, {
key: 'getHashForPathSync',
value: function getHashForPathSync(absoluteFilePath) {
var cacheKey = (0, _sanitizePaths2.default)(absoluteFilePath);
if (this.appRoot) {
cacheKey = cacheKey.replace(this.appRoot, '');
}
// NB: We do this because x-require will include an absolute path from the
// original built app and we need to still grok it
if (this.originalAppRoot) {
cacheKey = cacheKey.replace(this.originalAppRoot, '');
}
if (this.realAppRoot) {
cacheKey = cacheKey.replace(this.realAppRoot, '');
}
var cacheEntry = this.changeCache[cacheKey];
if (this.failOnCacheMiss) {
if (!cacheEntry) {
d('Tried to read file cache entry for ' + absoluteFilePath);
d('cacheKey: ' + cacheKey + ', appRoot: ' + this.appRoot + ', originalAppRoot: ' + this.originalAppRoot);
throw new Error('Asked for ' + absoluteFilePath + ' but it was not precompiled!');
}
return cacheEntry.info;
}
var stat = _fs2.default.statSync(absoluteFilePath);
var ctime = stat.ctime.getTime();
var size = stat.size;
if (!stat || !stat.isFile()) throw new Error('Can\'t stat ' + absoluteFilePath);
if (cacheEntry) {
if (cacheEntry.ctime >= ctime && cacheEntry.size === size) {
return cacheEntry.info;
}
d('Invalidating cache entry: ' + cacheEntry.ctime + ' === ' + ctime + ' && ' + cacheEntry.size + ' === ' + size);
delete this.changeCache.cacheEntry;
}
var _calculateHashForFile = this.calculateHashForFileSync(absoluteFilePath);
var digest = _calculateHashForFile.digest;
var sourceCode = _calculateHashForFile.sourceCode;
var binaryData = _calculateHashForFile.binaryData;
var info = {
hash: digest,
isMinified: FileChangedCache.contentsAreMinified(sourceCode || ''),
isInNodeModules: FileChangedCache.isInNodeModules(absoluteFilePath),
hasSourceMap: FileChangedCache.hasSourceMap(sourceCode || ''),
isFileBinary: !!binaryData
};
this.changeCache[cacheKey] = { ctime: ctime, size: size, info: info };
d('Cache entry for ' + cacheKey + ': ' + (0, _stringify2.default)(this.changeCache[cacheKey]));
if (binaryData) {
return _lodash2.default.extend({ binaryData: binaryData }, info);
} else {
return _lodash2.default.extend({ sourceCode: sourceCode }, info);
}
}
}, {
key: 'saveSync',
value: function saveSync(filePath) {
var toSave = this.getSavedData();
var buf = _zlib2.default.gzipSync(new Buffer((0, _stringify2.default)(toSave)));
_fs2.default.writeFileSync(filePath, buf);
}
}, {
key: 'calculateHashForFileSync',
value: function calculateHashForFileSync(absoluteFilePath) {
var buf = _fs2.default.readFileSync(absoluteFilePath);
var encoding = FileChangedCache.detectFileEncoding(buf);
if (!encoding) {
var _digest2 = _crypto2.default.createHash('sha1').update(buf).digest('hex');
return { sourceCode: null, digest: _digest2, binaryData: buf };
}
var sourceCode = _fs2.default.readFileSync(absoluteFilePath, encoding);
var digest = _crypto2.default.createHash('sha1').update(sourceCode, 'utf8').digest('hex');
return { sourceCode: sourceCode, digest: digest, binaryData: null };
}
/**
* Determines via some statistics whether a file is likely to be minified.
*
* @private
*/
}], [{
key: 'loadFromData',
value: function loadFromData(data, appRoot) {
var failOnCacheMiss = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2];
var ret = new FileChangedCache(appRoot, failOnCacheMiss);
ret.changeCache = data.changeCache;
ret.originalAppRoot = data.appRoot;
return ret;
}
/**
* Allows you to create a FileChangedCache from serialized data saved from
* {@link save}.
*
* @param {string} file Saved data from save.
*
* @param {string} appRoot The top-level directory for your application (i.e.
* the one which has your package.json).
*
* @param {boolean} failOnCacheMiss (optional) If True, cache misses will throw.
*
* @return {Promise<FileChangedCache>}
*/
}, {
key: 'loadFromFile',
value: function () {
var ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(file, appRoot) {
var failOnCacheMiss = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2];
var buf;
return _regenerator2.default.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
d('Loading canned FileChangedCache from ' + file);
_context4.next = 3;
return _promise.pfs.readFile(file);
case 3:
buf = _context4.sent;
_context4.t0 = FileChangedCache;
_context4.t1 = JSON;
_context4.next = 8;
return _promise.pzlib.gunzip(buf);
case 8:
_context4.t2 = _context4.sent;
_context4.t3 = _context4.t1.parse.call(_context4.t1, _context4.t2);
_context4.t4 = appRoot;
_context4.t5 = failOnCacheMiss;
return _context4.abrupt('return', _context4.t0.loadFromData.call(_context4.t0, _context4.t3, _context4.t4, _context4.t5));
case 13:
case 'end':
return _context4.stop();
}
}
}, _callee4, this);
}));
return function loadFromFile(_x7, _x8) {
return ref.apply(this, arguments);
};
}()
}, {
key: 'contentsAreMinified',
value: function contentsAreMinified(source) {
var length = source.length;
if (length > 1024) length = 1024;
var newlineCount = 0;
// Roll through the characters and determine the average line length
for (var i = 0; i < source.length; i++) {
if (source[i] === '\n') newlineCount++;
}
// No Newlines? Any file other than a super small one is minified
if (newlineCount === 0) {
return length > 80;
}
var avgLineLength = length / newlineCount;
return avgLineLength > 80;
}
/**
* Determines whether a path is in node_modules or the Electron init code
*
* @private
*/
}, {
key: 'isInNodeModules',
value: function isInNodeModules(filePath) {
return !!(filePath.match(/node_modules[\\\/]/i) || filePath.match(/atom\.asar/));
}
/**
* Returns whether a file has an inline source map
*
* @private
*/
}, {
key: 'hasSourceMap',
value: function hasSourceMap(sourceCode) {
return sourceCode.lastIndexOf('//# sourceMap') > sourceCode.lastIndexOf('\n');
}
/**
* Determines the encoding of a file from the two most common encodings by trying
* to decode it then looking for encoding errors
*
* @private
*/
}, {
key: 'detectFileEncoding',
value: function detectFileEncoding(buffer) {
if (buffer.length < 1) return false;
var buf = buffer.length < 4096 ? buffer : buffer.slice(0, 4096);
var encodings = ['utf8', 'utf16le'];
var encoding = _lodash2.default.find(encodings, function (x) {
return !FileChangedCache.containsControlCharacters(buf.toString(x));
});
return encoding;
}
/**
* Determines whether a string is likely to be poorly encoded by looking for
* control characters above a certain threshold
*
* @private
*/
}, {
key: 'containsControlCharacters',
value: function containsControlCharacters(str) {
var controlCount = 0;
var threshold = str.length < 64 ? 2 : 16;
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
if (c === 65536 || c < 8) controlCount++;
if (controlCount > threshold) return true;
}
if (controlCount === 0) return false;
return controlCount / str.length < 0.02;
}
}]);
return FileChangedCache;
}();
exports.default = FileChangedCache;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9maWxlLWNoYW5nZS1jYWNoZS5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQU9BLElBQU0sSUFBSSxRQUFRLE9BQVIsRUFBaUIsb0NBQWpCLENBQUo7Ozs7Ozs7Ozs7Ozs7O0lBYWU7QUFDbkIsV0FEbUIsZ0JBQ25CLENBQVksT0FBWixFQUE0QztRQUF2Qix3RUFBZ0IscUJBQU87d0NBRHpCLGtCQUN5Qjs7QUFDMUMsU0FBSyxPQUFMLEdBQWUsNkJBQWlCLE9BQWpCLENBQWYsQ0FEMEM7O0FBRzFDLFNBQUssZUFBTCxHQUF1QixlQUF2QixDQUgwQztBQUkxQyxTQUFLLFdBQUwsR0FBbUIsRUFBbkIsQ0FKMEM7R0FBNUM7Ozs7Ozs7Ozs7Ozs7Ozs7OzZCQURtQjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7NEZBcUVFO1lBQ2YsVUFXQSxZQVlBLE1BQ0EsT0FDQSxZQVlDLFFBQVEsWUFBWSxZQUVyQjs7Ozs7O0FBdkNBLDJCQUFXLDZCQUFpQixnQkFBakI7O0FBQ2Ysb0JBQUksS0FBSyxPQUFMLEVBQWM7QUFDaEIsNkJBQVcsU0FBUyxPQUFULENBQWlCLEtBQUssT0FBTCxFQUFjLEVBQS9CLENBQVgsQ0FEZ0I7aUJBQWxCOzs7O0FBTUEsb0JBQUksS0FBSyxlQUFMLEVBQXNCO0FBQ3hCLDZCQUFXLFNBQVMsT0FBVCxDQUFpQixLQUFLLGVBQUwsRUFBc0IsRUFBdkMsQ0FBWCxDQUR3QjtpQkFBMUI7O0FBSUksNkJBQWEsS0FBSyxXQUFMLENBQWlCLFFBQWpCOztxQkFFYixLQUFLLGVBQUw7Ozs7O29CQUNHOzs7OztBQUNILDBEQUF3QyxnQkFBeEM7QUFDQSxpQ0FBZSwyQkFBc0IsS0FBSyxPQUFMLDJCQUFrQyxLQUFLLGVBQUwsQ0FBdkU7c0JBQ00sSUFBSSxLQUFKLGdCQUF1QixpREFBdkI7OztpREFHRCxXQUFXLElBQVg7Ozs7dUJBR1EsYUFBSSxJQUFK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LElBQUksTUFBSixFQUFZLEdBQTlCLEVBQW1DO0FBQ2pDLFlBQUksSUFBSSxJQUFJLFVBQUosQ0FBZSxDQUFmLENBQUosQ0FENkI7QUFFakMsWUFBSSxNQUFNLEtBQU4sSUFBZSxJQUFJLENBQUosRUFBTyxlQUExQjs7QUFFQSxZQUFJLGVBQWUsU0FBZixFQUEwQixPQUFPLElBQVAsQ0FBOUI7T0FKRjs7QUFPQSxVQUFJLGlCQUFpQixDQUFqQixFQUFvQixPQUFPLEtBQVAsQ0FBeEI7QUFDQSxhQUFPLFlBQUMsR0FBZSxJQUFJLE1BQUosR0FBYyxJQUE5QixDQVo2Qjs7O1NBalVuQiIsImZpbGUiOiJmaWxlLWNoYW5nZS1jYWNoZS5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBmcyBmcm9tICdmcyc7XG5pbXBvcnQgemxpYiBmcm9tICd6bGliJztcbmltcG9ydCBjcnlwdG8gZnJvbSAnY3J5cHRvJztcbmltcG9ydCB7cGZzLCBwemxpYn0gZnJvbSAnLi9wcm9taXNlJztcbmltcG9ydCBfIGZyb20gJ2xvZGFzaCc7XG5pbXBvcnQgc2FuaXRpemVGaWxlUGF0aCBmcm9tICcuL3Nhbml0aXplLXBhdGhzJztcblxuY29uc3QgZCA9IHJlcXVpcmUoJ2RlYnVnJykoJ2VsZWN0cm9uLWNvbXBpbGU6ZmlsZS1jaGFuZ2UtY2FjaGUnKTtcblxuLyoqXG4gKiBUaGlzIGNsYXNzIGNhY2hlcyBpbmZvcm1hdGlvbiBhYm91dCBmaWxlcyBhbmQgZGV0ZXJtaW5lcyB3aGV0aGVyIHRoZXkgaGF2ZVxuICogY2hhbmdlZCBjb250ZW50cyBvciBub3QuIE1vc3QgaW1wb3J0YW50bHksIHRoaXMgY2xhc3MgY2FjaGVzIHRoZSBoYXNoIG9mIHNlZW5cbiAqIGZpbGVzIHNvIHRoYXQgYXQgZGV2ZWxvcG1lbnQgdGltZSwgd2UgZG9uJ3QgaGF2ZSB0byByZWNhbGN1bGF0ZSB0aGVtIGNvbnN0YW50bHkuXG4gKlxuICogVGhpcyBjbGFzcyBpcyBhbHNvIHRoZSBjb3JlIG9mIGhvdyBlbGVjdHJvbi1jb21waWxlIHJ1bnMgcXVpY2tseSBpbiBwcm9kdWN0aW9uXG4gKiBtb2RlIC0gYWZ0ZXIgcHJlY29tcGlsYXRpb24sIHRoZSBjYWNoZSBpcyBzZXJpYWxpemVkIGFsb25nIHdpdGggdGhlIHJlc3Qgb2YgdGhlXG4gKiBkYXRhIGluIHtAbGluayBDb21waWxlckhvc3R9LCBzbyB0aGF0IHdoZW4gd2UgbG9hZCB0aGUgYXBwIGluIHByb2R1Y3Rpb24gbW9kZSxcbiAqIHdlIGRvbid0IGVuZCB1cCBjYWxjdWxhdGluZyBoYXNoZXMgb2YgZmlsZSBjb250ZW50IGF0IGFsbCwgb25seSB1c2luZyB0aGUgY29udGVudHNcbiAqIG9mIHRoaXMgY2FjaGUuXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEZpbGVDaGFuZ2VkQ2FjaGUge1xuICBjb25zdHJ1Y3RvcihhcHBSb290LCBmYWlsT25DYWNoZU1pc3M9ZmFsc2UpIHtcbiAgICB0aGlzLmFwcFJvb3QgPSBzYW5pdGl6ZUZpbGVQYXRoKGFwcFJvb3QpO1xuXG4gICAgdGhpcy5mYWlsT25DYWNoZU1pc3MgPSBmYWlsT25DYWNoZU1pc3M7XG4gICAgdGhpcy5jaGFuZ2VDYWNoZSA9IHt9O1xuICB9XG5cbiAgLyoqXG4gICAqIEFsbG93cyB5b3UgdG8gY3JlYXRlIGEgRmlsZUNoYW5nZWRDYWNoZSBmcm9tIHNlcmlhbGl6ZWQgZGF0YSBzYXZlZCBmcm9tXG4gICAqIHtAbGluayBnZXRTYXZlZERhdGF9LlxuICAgKlxuICAgKiBAcGFyYW0gIHtPYmplY3R9IGRhdGEgIFNhdmVkIGRhdGEgZnJvbSBnZXRTYXZlZERhdGEuXG4gICAqXG4gICAqIEBwYXJhbSAge3N0cmluZ30gYXBwUm9vdCAgVGhlIHRvcC1sZXZlbCBkaXJlY3RvcnkgZm9yIHlvdXIgYXBwbGljYXRpb24gKGkuZS5cbiAgICogICAgICAgICAgICAgICAgICAgICAgICAgICB0aGUgb25lIHdoaWNoIGhhcyB5b3VyIHBhY2thZ2UuanNvbikuXG4gICAqXG4gICAqIEBwYXJhbSAge2Jvb2xlYW59IGZhaWxPbkNhY2hlTWlzcyAob3B0aW9uYWwpICBJZiBUcnVlLCBjYWNoZSBtaXNzZXMgd2lsbCB0aHJvdy5cbiAgICpcbiAgICogQHJldHVybiB7RmlsZUNoYW5nZWRDYWNoZX1cbiAgICovXG4gIHN0YXRpYyBsb2FkRnJvbURhdGEoZGF0YSwgYXBwUm9vdCwgZmFpbE9uQ2FjaGVNaXNzPXRydWUpIHtcbiAgICBsZXQgcmV0ID0gbmV3IEZpbGVDaGFuZ2VkQ2FjaGUoYXBwUm9vdCwgZmFpbE9uQ2FjaGVNaXNzKTtcbiAgICByZXQuY2hhbmdlQ2FjaGUgPSBkYXRhLmNoYW5nZUNhY2hlO1xuICAgIHJldC5vcmlnaW5hbEFwcFJvb3QgPSBkYXRhLmFwcFJvb3Q7XG5cbiAgICByZXR1cm4gcmV0O1xuICB9XG5cblxuICAvKipcbiAgICogQWxsb3dzIHlvdSB0byBjcmVhdGUgYSBGaWxlQ2hhbmdlZENhY2hlIGZyb20gc2VyaWFsaXplZCBkYXRhIHNhdmVkIGZyb21cbiAgICoge0BsaW5rIHNhdmV9LlxuICAgKlxuICAgKiBAcGFyYW0gIHtzdHJpbmd9IGZpbGUgIFNhdmVkIGRhdGEgZnJvbSBzYXZlLlxuICAgKlxuICAgKiBAcGFyYW0gIHtzdHJpbmd9IGFwcFJvb3QgIFRoZSB0b3AtbGV2ZWwgZGlyZWN0b3J5IGZvciB5b3VyIGFwcGxpY2F0aW9uIChpLmUuXG4gICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIG9uZSB3aGljaCBoYXMgeW91ciBwYWNrYWdlLmpzb24pLlxuICAgKlxuICAgKiBAcGFyYW0gIHtib29sZWFufSBmYWlsT25DYWNoZU1pc3MgKG9wdGlvbmFsKSAgSWYgVHJ1ZSwgY2FjaGUgbWlzc2VzIHdpbGwgdGhyb3cuXG4gICAqXG4gICAqIEByZXR1cm4ge1Byb21pc2U8RmlsZUNoYW5nZWRDYWNoZT59XG4gICAqL1xuICBzdGF0aWMgYXN5bmMgbG9hZEZyb21GaWxlKGZpbGUsIGFwcFJvb3QsIGZhaWxPbkNhY2hlTWlzcz10cnVlKSB7XG4gICAgZChgTG9hZGluZyBjYW5uZWQgRmlsZUNoYW5nZWRDYWNoZSBmcm9tICR7ZmlsZX1gKTtcblxuICAgIGxldCBidWYgPSBhd2FpdCBwZnMucmVhZEZpbGUoZmlsZSk7XG4gICAgcmV0dXJuIEZpbGVDaGFuZ2VkQ2FjaGUubG9hZEZyb21EYXRhKEpTT04ucGFyc2UoYXdhaXQgcHpsaWIuZ3VuemlwKGJ1ZikpLCBhcHBSb290LCBmYWlsT25DYWNoZU1pc3MpO1xuICB9XG5cblxuICAvKipcbiAgICogUmV0dXJucyBpbmZvcm1hdGlvbiBhYm91dCBhIGdpdmVuIGZpbGUsIGluY2x1ZGluZyBpdHMgaGFzaC4gVGhpcyBtZXRob2QgaXNcbiAgICogdGhlIG1haW4gbWV0aG9kIGZvciB0aGlzIGNhY2hlLlxuICAgKlxuICAgKiBAcGFyYW0gIHtzdHJpbmd9IGFic29sdXRlRmlsZVBhdGggIFRoZSBwYXRoIHRvIGEgZmlsZSB0byByZXRyaWV2ZSBpbmZvIG9uLlxuICAgKlxuICAgKiBAcmV0dXJuIHtQcm9taXNlPE9iamVjdD59XG4gICAqXG4gICAqIEBwcm9wZXJ0eSB7c3RyaW5nfSBoYXNoICBUaGUgU0hBMSBoYXNoIG9mIHRoZSBmaWxlXG4gICAqIEBwcm9wZXJ0eSB7Ym9vbGVhbn0gaXNNaW5pZmllZCAgVHJ1ZSBpZiB0aGUgZmlsZSBpcyBtaW5pZmllZFxuICAgKiBAcHJvcGVydHkge2Jvb2xlYW59IGlzSW5Ob2RlTW9kdWxlcyAgVHJ1ZSBpZiB0aGUgZmlsZSBpcyBpbiBhIGxpYnJhcnkgZGlyZWN0b3J5XG4gICAqIEBwcm9wZXJ0eSB7Ym9vbGVhbn0gaGFzU291cmNlTWFwICBUcnVlIGlmIHRoZSBmaWxlIGhhcyBhIHNvdXJjZSBtYXBcbiAgICogQHByb3BlcnR5IHtib29sZWFufSBpc0ZpbGVCaW5hcnkgIFRydWUgaWYgdGhlIGZpbGUgaXMgbm90IGEgdGV4dCBmaWxlXG4gICAqIEBwcm9wZXJ0eSB7QnVmZmVyfSBiaW5hcnlEYXRhIChvcHRpb25hbCkgIFRoZSBidWZmZXIgdGhhdCB3YXMgcmVhZCBpZiB0aGUgZmlsZVxuICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3YXMgYmluYXJ5IGFuZCB0aGVyZSB3YXMgYSBjYWNoZSBtaXNzLlxuICAgKiBAcHJvcGVydHkge3N0cmluZ30gY29kZSAob3B0aW9uYWwpICBUaGUgc3RyaW5nIHRoYXQgd2FzIHJlYWQgaWYgdGhlIGZpbGVcbiAgICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2FzIHRleHQgYW5kIHRoZXJlIHdhcyBhIGNhY2hlIG1pc3NcbiAgICovXG4gIGFzeW5jIGdldEhhc2hGb3JQYXRoKGFic29sdXRlRmlsZVBhdGgpIHtcbiAgICBsZXQgY2FjaGVLZXkgPSBzYW5pdGl6ZUZpbGVQYXRoKGFic29sdXRlRmlsZVBhdGgpO1xuICAgIGlmICh0aGlzLmFwcFJvb3QpIHtcbiAgICAgIGNhY2hlS2V5ID0gY2FjaGVLZXkucmVwbGFjZSh0aGlzLmFwcFJvb3QsICcnKTtcbiAgICB9XG5cbiAgICAvLyBOQjogV2UgZG8gdGhpcyBiZWNhdXNlIHgtcmVxdWlyZSB3aWxsIGluY2x1ZGUgYW4gYWJzb2x1dGUgcGF0aCBmcm9tIHRoZVxuICAgIC8vIG9yaWdpbmFsIGJ1aWx0IGFwcCBhbmQgd2UgbmVlZCB0byBzdGlsbCBncm9rIGl0XG4gICAgaWYgKHRoaXMub3JpZ2luYWxBcHBSb290KSB7XG4gICAgICBjYWNoZUtleSA9IGNhY2hlS2V5LnJlcGxhY2UodGhpcy5vcmlnaW5hbEFwcFJvb3QsICcnKTtcbiAgICB9XG5cbiAgICBsZXQgY2FjaGVFbnRyeSA9IHRoaXMuY2hhbmdlQ2FjaGVbY2FjaGVLZXldO1xuXG4gICAgaWYgKHRoaXMuZmFpbE9uQ2FjaGVNaXNzKSB7XG4gICAgICBpZiAoIWNhY2hlRW50cnkpIHtcbiAgICAgICAgZChgVHJpZWQgdG8gcmVhZCBmaWxlIGNhY2hlIGVudHJ5IGZvciAke2Fic29sdXRlRmlsZVBhdGh9YCk7XG4gICAgICAgIGQoYGNhY2hlS2V5OiAke2NhY2hlS2V5fSwgYXBwUm9vdDogJHt0aGlzLmFwcFJvb3R9LCBvcmlnaW5hbEFwcFJvb3Q6ICR7dGhpcy5vcmlnaW5hbEFwcFJvb3R9YCk7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgQXNrZWQgZm9yICR7YWJzb2x1dGVGaWxlUGF0aH0gYnV0IGl0IHdhcyBub3QgcHJlY29tcGlsZWQhYCk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBjYWNoZUVudHJ5LmluZm87XG4gICAgfVxuXG4gICAgbGV0IHN0YXQgPSBhd2FpdCBwZnMuc3RhdChhYnNvbHV0ZUZpbGVQYXRoKTtcbiAgICBsZXQgY3RpbWUgPSBzdGF0LmN0aW1lLmdldFRpbWUoKTtcbiAgICBsZXQgc2l6ZSA9IHN0YXQuc2l6ZTtcbiAgICBpZiAoIXN0YXQgfHwgIXN0YXQuaXNGaWxlKCkpIHRocm93IG5ldyBFcnJvcihgQ2FuJ3Qgc3RhdCAke2Fic29sdXRlRmlsZVBhdGh9YCk7XG5cbiAgICBpZiAoY2FjaGVFbnRyeSkge1xuICAgICAgaWYgKGNhY2hlRW50cnkuY3RpbWUgPj0gY3RpbWUgJiYgY2FjaGVFbnRyeS5zaXplID09PSBzaXplKSB7XG4gICAgICAgIHJldHVybiBjYWNoZUVudHJ5LmluZm87XG4gICAgICB9XG5cbiAgICAgIGQoYEludmFsaWRhdGluZyBjYWNoZSBlbnRyeTogJHtjYWNoZUVudHJ5LmN0aW1lfSA9PT0gJHtjdGltZX0gJiYgJHtjYWNoZUVudHJ5LnNpemV9ID09PSAke3NpemV9YCk7XG4gICAgICBkZWxldGUgdGhpcy5jaGFuZ2VDYWNoZS5jYWNoZUVudHJ5O1xuICAgIH1cblxuICAgIGxldCB7ZGlnZXN0LCBzb3VyY2VDb2RlLCBiaW5hcnlEYXRhfSA9IGF3YWl0IHRoaXMuY2FsY3VsYXRlSGFzaEZvckZpbGUoYWJzb2x1dGVGaWxlUGF0aCk7XG5cbiAgICBsZXQgaW5mbyA9IHtcbiAgICAgIGhhc2g6IGRpZ2VzdCxcbiAgICAgIGlzTWluaWZpZWQ6IEZpbGVDaGFuZ2VkQ2FjaGUuY29udGVudHNBcmVNaW5pZmllZChzb3VyY2VDb2RlIHx8ICcnKSxcbiAgICAgIGlzSW5Ob2RlTW9kdWxlczogRmlsZUNoYW5nZWRDYWNoZS5pc0luTm9kZU1vZHVsZXMoYWJzb2x1dGVGaWxlUGF0aCksXG4gICAgICBoYXNTb3VyY2VNYXA6IEZpbGVDaGFuZ2VkQ2FjaGUuaGFzU291cmNlTWFwKHNvdXJjZUNvZGUgfHwgJycpLFxuICAgICAgaXNGaWxlQmluYXJ5OiAhIWJpbmFyeURhdGFcbiAgICB9O1xuXG4gICAgdGhpcy5jaGFuZ2VDYWNoZVtjYWNoZUtleV0gPSB7IGN0aW1lLCBzaXplLCBpbmZvIH07XG4gICAgZChgQ2FjaGUgZW50cnkgZm9yICR7Y2FjaGVLZXl9OiAke0pTT04uc3RyaW5naWZ5KHRoaXMuY2hhbmdlQ2FjaGVbY2FjaGVLZXldKX1gKTtcblxuICAgIGlmIChiaW5hcnlEYXRhKSB7XG4gICAgICByZXR1cm4gXy5leHRlbmQoe2JpbmFyeURhdGF9LCBpbmZvKTtcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIF8uZXh0ZW5kKHtzb3VyY2VDb2RlfSwgaW5mbyk7XG4gICAgfVxuICB9XG5cblxuICAvKipcbiAgICogUmV0dXJucyBkYXRhIHRoYXQgY2FuIHBhc3NlZCB0byB7QGxpbmsgbG9hZEZyb21EYXRhfSB0byByZWh5ZHJhdGUgdGhpcyBjYWNoZS5cbiAgICpcbiAgICogQHJldHVybiB7T2JqZWN0fVxuICAgKi9cbiAgZ2V0U2F2ZWREYXRhKCkge1xuICAgIHJldHVybiB7IGNoYW5nZUNhY2hlOiB0aGlzLmNoYW5nZUNhY2hlLCBhcHBSb290OiB0aGlzLmFwcFJvb3QgfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTZXJpYWxpemVzIHRoaXMgb2JqZWN0J3MgZGF0YSB0byBhIGZpbGUuXG4gICAqXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBmaWxlUGF0aCAgVGhlIHBhdGggdG8gc2F2ZSBkYXRhIHRvLlxuICAgKlxuICAgKiBAcmV0dXJuIHtQcm9taXNlfSBDb21wbGV0aW9uLlxuICAgKi9cbiAgYXN5bmMgc2F2ZShmaWxlUGF0aCkge1xuICAgIGxldCB0b1NhdmUgPSB0aGlzLmdldFNhdmVkRGF0YSgpO1xuXG4gICAgbGV0IGJ1ZiA9IGF3YWl0IHB6bGliLmd6aXAobmV3IEJ1ZmZlcihKU09OLnN0cmluZ2lmeSh0b1NhdmUpKSk7XG4gICAgYXdhaXQgcGZzLndyaXRlRmlsZShmaWxlUGF0aCwgYnVmKTtcbiAgfVxuXG4gIGFzeW5jIGNhbGN1bGF0ZUhhc2hGb3JGaWxlKGFic29sdXRlRmlsZVBhdGgpIHtcbiAgICBsZXQgYnVmID0gYXdhaXQgcGZzLnJlYWRGaWxlKGFic29sdXRlRmlsZVBhdGgpO1xuICAgIGxldCBlbmNvZGluZyA9IEZpbGVDaGFuZ2VkQ2FjaGUuZGV0ZWN0RmlsZUVuY29kaW5nKGJ1Zik7XG5cbiAgICBpZiAoIWVuY29kaW5nKSB7XG4gICAgICBsZXQgZGlnZXN0ID0gY3J5cHRvLmNyZWF0ZUhhc2goJ3NoYTEnKS51cGRhdGUoYnVmKS5kaWdlc3QoJ2hleCcpO1xuICAgICAgcmV0dXJuIHsgc291cmNlQ29kZTogbnVsbCwgZGlnZXN0LCBiaW5hcnlEYXRhOiBidWYgfTtcbiAgICB9XG5cbiAgICBsZXQgc291cmNlQ29kZSA9IGF3YWl0IHBmcy5yZWFkRmlsZShhYnNvbHV0ZUZpbGVQYXRoLCBlbmNvZGluZyk7XG4gICAgbGV0IGRpZ2VzdCA9IGNyeXB0by5jcmVhdGVIYXNoKCdzaGExJykudXBkYXRlKHNvdXJjZUNvZGUsICd1dGY4JykuZGlnZXN0KCdoZXgnKTtcblxuICAgIHJldHVybiB7c291cmNlQ29kZSwgZGlnZXN0LCBiaW5hcnlEYXRhOiBudWxsIH07XG4gIH1cblxuICBnZXRIYXNoRm9yUGF0aFN5bmMoYWJzb2x1dGVGaWxlUGF0aCkge1xuICAgIGxldCBjYWNoZUtleSA9IHNhbml0aXplRmlsZVBhdGgoYWJzb2x1dGVGaWxlUGF0aCk7XG4gICAgaWYgKHRoaXMuYXBwUm9vdCkge1xuICAgICAgY2FjaGVLZXkgPSBjYWNoZUtleS5yZXBsYWNlKHRoaXMuYXBwUm9vdCwgJycpO1xuICAgIH1cblxuICAgIC8vIE5COiBXZSBkbyB0aGlzIGJlY2F1c2UgeC1yZXF1aXJlIHdpbGwgaW5jbHVkZSBhbiBhYnNvbHV0ZSBwYXRoIGZyb20gdGhlXG4gICAgLy8gb3JpZ2luYWwgYnVpbHQgYXBwIGFuZCB3ZSBuZWVkIHRvIHN0aWxsIGdyb2sgaXRcbiAgICBpZiAodGhpcy5vcmlnaW5hbEFwcFJvb3QpIHtcbiAgICAgIGNhY2hlS2V5ID0gY2FjaGVLZXkucmVwbGFjZSh0aGlzLm9yaWdpbmFsQXBwUm9vdCwgJycpO1xuICAgIH1cblxuICAgIGlmICh0aGlzLnJlYWxBcHBSb290KSB7XG4gICAgICBjYWNoZUtleSA9IGNhY2hlS2V5LnJlcGxhY2UodGhpcy5yZWFsQXBwUm9vdCwgJycpO1xuICAgIH1cblxuICAgIGxldCBjYWNoZUVudHJ5ID0gdGhpcy5jaGFuZ2VDYWNoZVtjYWNoZUtleV07XG5cbiAgICBpZiAodGhpcy5mYWlsT25DYWNoZU1pc3MpIHtcbiAgICAgIGlmICghY2FjaGVFbnRyeSkge1xuICAgICAgICBkKGBUcmllZCB0byByZWFkIGZpbGUgY2FjaGUgZW50cnkgZm9yICR7YWJzb2x1dGVGaWxlUGF0aH1gKTtcbiAgICAgICAgZChgY2FjaGVLZXk6ICR7Y2FjaGVLZXl9LCBhcHBSb290OiAke3RoaXMuYXBwUm9vdH0sIG9yaWdpbmFsQXBwUm9vdDogJHt0aGlzLm9yaWdpbmFsQXBwUm9vdH1gKTtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBBc2tlZCBmb3IgJHthYnNvbHV0ZUZpbGVQYXRofSBidXQgaXQgd2FzIG5vdCBwcmVjb21waWxlZCFgKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIGNhY2hlRW50cnkuaW5mbztcbiAgICB9XG5cbiAgICBsZXQgc3RhdCA9IGZzLnN0YXRTeW5jKGFic29sdXRlRmlsZVBhdGgpO1xuICAgIGxldCBjdGltZSA9IHN0YXQuY3RpbWUuZ2V0VGltZSgpO1xuICAgIGxldCBzaXplID0gc3RhdC5zaXplO1xuICAgIGlmICghc3RhdCB8fCAhc3RhdC5pc0ZpbGUoKSkgdGhyb3cgbmV3IEVycm9yKGBDYW4ndCBzdGF0ICR7YWJzb2x1dGVGaWxlUGF0aH1gKTtcblxuICAgIGlmIChjYWNoZUVudHJ5KSB7XG4gICAgICBpZiAoY2FjaGVFbnRyeS5jdGltZSA+PSBjdGltZSAmJiBjYWNoZUVudHJ5LnNpemUgPT09IHNpemUpIHtcbiAgICAgICAgcmV0dXJuIGNhY2hlRW50cnkuaW5mbztcbiAgICAgIH1cblxuICAgICAgZChgSW52YWxpZGF0aW5nIGNhY2hlIGVudHJ5OiAke2NhY2hlRW50cnkuY3RpbWV9ID09PSAke2N0aW1lfSAmJiAke2NhY2hlRW50cnkuc2l6ZX0gPT09ICR7c2l6ZX1gKTtcbiAgICAgIGRlbGV0ZSB0aGlzLmNoYW5nZUNhY2hlLmNhY2hlRW50cnk7XG4gICAgfVxuXG4gICAgbGV0IHtkaWdlc3QsIHNvdXJjZUNvZGUsIGJpbmFyeURhdGF9ID0gdGhpcy5jYWxjdWxhdGVIYXNoRm9yRmlsZVN5bmMoYWJzb2x1dGVGaWxlUGF0aCk7XG5cbiAgICBsZXQgaW5mbyA9IHtcbiAgICAgIGhhc2g6IGRpZ2VzdCxcbiAgICAgIGlzTWluaWZpZWQ6IEZpbGVDaGFuZ2VkQ2FjaGUuY29udGVudHNBcmVNaW5pZmllZChzb3VyY2VDb2RlIHx8ICcnKSxcbiAgICAgIGlzSW5Ob2RlTW9kdWxlczogRmlsZUNoYW5nZWRDYWNoZS5pc0luTm9kZU1vZHVsZXMoYWJzb2x1dGVGaWxlUGF0aCksXG4gICAgICBoYXNTb3VyY2VNYXA6IEZpbGVDaGFuZ2VkQ2FjaGUuaGFzU291cmNlTWFwKHNvdXJjZUNvZGUgfHwgJycpLFxuICAgICAgaXNGaWxlQmluYXJ5OiAhIWJpbmFyeURhdGFcbiAgICB9O1xuXG4gICAgdGhpcy5jaGFuZ2VDYWNoZVtjYWNoZUtleV0gPSB7IGN0aW1lLCBzaXplLCBpbmZvIH07XG4gICAgZChgQ2FjaGUgZW50cnkgZm9yICR7Y2FjaGVLZXl9OiAke0pTT04uc3RyaW5naWZ5KHRoaXMuY2hhbmdlQ2FjaGVbY2FjaGVLZXldKX1gKTtcblxuICAgIGlmIChiaW5hcnlEYXRhKSB7XG4gICAgICByZXR1cm4gXy5leHRlbmQoe2JpbmFyeURhdGF9LCBpbmZvKTtcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIF8uZXh0ZW5kKHtzb3VyY2VDb2RlfSwgaW5mbyk7XG4gICAgfVxuICB9XG5cbiAgc2F2ZVN5bmMoZmlsZVBhdGgpIHtcbiAgICBsZXQgdG9TYXZlID0gdGhpcy5nZXRTYXZlZERhdGEoKTtcblxuICAgIGxldCBidWYgPSB6bGliLmd6aXBTeW5jKG5ldyBCdWZmZXIoSlNPTi5zdHJpbmdpZnkodG9TYXZlKSkpO1xuICAgIGZzLndyaXRlRmlsZVN5bmMoZmlsZVBhdGgsIGJ1Zik7XG4gIH1cblxuICBjYWxjdWxhdGVIYXNoRm9yRmlsZVN5bmMoYWJzb2x1dGVGaWxlUGF0aCkge1xuICAgIGxldCBidWYgPSBmcy5yZWFkRmlsZVN5bmMoYWJzb2x1dGVGaWxlUGF0aCk7XG4gICAgbGV0IGVuY29kaW5nID0gRmlsZUNoYW5nZWRDYWNoZS5kZXRlY3RGaWxlRW5jb2RpbmcoYnVmKTtcblxuICAgIGlmICghZW5jb2RpbmcpIHtcbiAgICAgIGxldCBkaWdlc3QgPSBjcnlwdG8uY3JlYXRlSGFzaCgnc2hhMScpLnVwZGF0ZShidWYpLmRpZ2VzdCgnaGV4Jyk7XG4gICAgICByZXR1cm4geyBzb3VyY2VDb2RlOiBudWxsLCBkaWdlc3QsIGJpbmFyeURhdGE6IGJ1Zn07XG4gICAgfVxuXG4gICAgbGV0IHNvdXJjZUNvZGUgPSBmcy5yZWFkRmlsZVN5bmMoYWJzb2x1dGVGaWxlUGF0aCwgZW5jb2RpbmcpO1xuICAgIGxldCBkaWdlc3QgPSBjcnlwdG8uY3JlYXRlSGFzaCgnc2hhMScpLnVwZGF0ZShzb3VyY2VDb2RlLCAndXRmOCcpLmRpZ2VzdCgnaGV4Jyk7XG5cbiAgICByZXR1cm4ge3NvdXJjZUNvZGUsIGRpZ2VzdCwgYmluYXJ5RGF0YTogbnVsbH07XG4gIH1cblxuXG4gIC8qKlxuICAgKiBEZXRlcm1pbmVzIHZpYSBzb21lIHN0YXRpc3RpY3Mgd2hldGhlciBhIGZpbGUgaXMgbGlrZWx5IHRvIGJlIG1pbmlmaWVkLlxuICAgKlxuICAgKiBAcHJpdmF0ZVxuICAgKi9cbiAgc3RhdGljIGNvbnRlbnRzQXJlTWluaWZpZWQoc291cmNlKSB7XG4gICAgbGV0IGxlbmd0aCA9IHNvdXJjZS5sZW5ndGg7XG4gICAgaWYgKGxlbmd0aCA+IDEwMjQpIGxlbmd0aCA9IDEwMjQ7XG5cbiAgICBsZXQgbmV3bGluZUNvdW50ID0gMDtcblxuICAgIC8vIFJvbGwgdGhyb3VnaCB0aGUgY2hhcmFjdGVycyBhbmQgZGV0ZXJtaW5lIHRoZSBhdmVyYWdlIGxpbmUgbGVuZ3RoXG4gICAgZm9yKGxldCBpPTA7IGkgPCBzb3VyY2UubGVuZ3RoOyBpKyspIHtcbiAgICAgIGlmIChzb3VyY2VbaV0gPT09ICdcXG4nKSBuZXdsaW5lQ291bnQrKztcbiAgICB9XG5cbiAgICAvLyBObyBOZXdsaW5lcz8gQW55IGZpbGUgb3RoZXIgdGhhbiBhIHN1cGVyIHNtYWxsIG9uZSBpcyBtaW5pZmllZFxuICAgIGlmIChuZXdsaW5lQ291bnQgPT09IDApIHtcbiAgICAgIHJldHVybiAobGVuZ3RoID4gODApO1xuICAgIH1cblxuICAgIGxldCBhdmdMaW5lTGVuZ3RoID0gbGVuZ3RoIC8gbmV3bGluZUNvdW50O1xuICAgIHJldHVybiAoYXZnTGluZUxlbmd0aCA+IDgwKTtcbiAgfVxuXG5cbiAgLyoqXG4gICAqIERldGVybWluZXMgd2hldGhlciBhIHBhdGggaXMgaW4gbm9kZV9tb2R1bGVzIG9yIHRoZSBFbGVjdHJvbiBpbml0IGNvZGVcbiAgICpcbiAgICogQHByaXZhdGVcbiAgICovXG4gIHN0YXRpYyBpc0luTm9kZU1vZHVsZXMoZmlsZVBhdGgpIHtcbiAgICByZXR1cm4gISEoZmlsZVBhdGgubWF0Y2goL25vZGVfbW9kdWxlc1tcXFxcXFwvXS9pKSB8fCBmaWxlUGF0aC5tYXRjaCgvYXRvbVxcLmFzYXIvKSk7XG4gIH1cblxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHdoZXRoZXIgYSBmaWxlIGhhcyBhbiBpbmxpbmUgc291cmNlIG1hcFxuICAgKlxuICAgKiBAcHJpdmF0ZVxuICAgKi9cbiAgc3RhdGljIGhhc1NvdXJjZU1hcChzb3VyY2VDb2RlKSB7XG4gICAgcmV0dXJuIHNvdXJjZUNvZGUubGFzdEluZGV4T2YoJy8vIyBzb3VyY2VNYXAnKSA+IHNvdXJjZUNvZGUubGFzdEluZGV4T2YoJ1xcbicpO1xuICB9XG5cbiAgLyoqXG4gICAqIERldGVybWluZXMgdGhlIGVuY29kaW5nIG9mIGEgZmlsZSBmcm9tIHRoZSB0d28gbW9zdCBjb21tb24gZW5jb2RpbmdzIGJ5IHRyeWluZ1xuICAgKiB0byBkZWNvZGUgaXQgdGhlbiBsb29raW5nIGZvciBlbmNvZGluZyBlcnJvcnNcbiAgICpcbiAgICogQHByaXZhdGVcbiAgICovXG4gIHN0YXRpYyBkZXRlY3RGaWxlRW5jb2RpbmcoYnVmZmVyKSB7XG4gICAgaWYgKGJ1ZmZlci5sZW5ndGggPCAxKSByZXR1cm4gZmFsc2U7XG4gICAgbGV0IGJ1ZiA9IChidWZmZXIubGVuZ3RoIDwgNDA5NiA/IGJ1ZmZlciA6IGJ1ZmZlci5zbGljZSgwLCA0MDk2KSk7XG5cbiAgICBjb25zdCBlbmNvZGluZ3MgPSBbJ3V0ZjgnLCAndXRmMTZsZSddO1xuXG4gICAgbGV0IGVuY29kaW5nID0gXy5maW5kKFxuICAgICAgZW5jb2RpbmdzLFxuICAgICAgKHgpID0+ICFGaWxlQ2hhbmdlZENhY2hlLmNvbnRhaW5zQ29udHJvbENoYXJhY3RlcnMoYnVmLnRvU3RyaW5nKHgpKSk7XG5cbiAgICByZXR1cm4gZW5jb2Rpbmc7XG4gIH1cblxuICAvKipcbiAgICogRGV0ZXJtaW5lcyB3aGV0aGVyIGEgc3RyaW5nIGlzIGxpa2VseSB0byBiZSBwb29ybHkgZW5jb2RlZCBieSBsb29raW5nIGZvclxuICAgKiBjb250cm9sIGNoYXJhY3RlcnMgYWJvdmUgYSBjZXJ0YWluIHRocmVzaG9sZFxuICAgKlxuICAgKiBAcHJpdmF0ZVxuICAgKi9cbiAgc3RhdGljIGNvbnRhaW5zQ29udHJvbENoYXJhY3RlcnMoc3RyKSB7XG4gICAgbGV0IGNvbnRyb2xDb3VudCA9IDA7XG4gICAgbGV0IHRocmVzaG9sZCA9IChzdHIubGVuZ3RoIDwgNjQgPyAyIDogMTYpO1xuXG4gICAgZm9yIChsZXQgaT0wOyBpIDwgc3RyLmxlbmd0aDsgaSsrKSB7XG4gICAgICBsZXQgYyA9IHN0ci5jaGFyQ29kZUF0KGkpO1xuICAgICAgaWYgKGMgPT09IDY1NTM2IHx8IGMgPCA4KSBjb250cm9sQ291bnQrKztcblxuICAgICAgaWYgKGNvbnRyb2xDb3VudCA+IHRocmVzaG9sZCkgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgaWYgKGNvbnRyb2xDb3VudCA9PT0gMCkgcmV0dXJuIGZhbHNlO1xuICAgIHJldHVybiAoY29udHJvbENvdW50IC8gc3RyLmxlbmd0aCkgPCAwLjAyO1xuICB9XG59XG4iXX0=