electron-compile
Version:
Electron supporting package to compile JS and CSS in Electron applications
604 lines (477 loc) • 46.7 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _regenerator = require('babel-runtime/regenerator');
var _regenerator2 = _interopRequireDefault(_regenerator);
var _assign = require('babel-runtime/core-js/object/assign');
var _assign2 = _interopRequireDefault(_assign);
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 _sanitizePaths = require('./sanitize-paths');
var _sanitizePaths2 = _interopRequireDefault(_sanitizePaths);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var d = require('debug-electron')('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 ? arguments[1] : false;
(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, _ref2, 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:
_ref2 = _context.sent;
digest = _ref2.digest;
sourceCode = _ref2.sourceCode;
binaryData = _ref2.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', (0, _assign2.default)({ binaryData: binaryData }, info));
case 35:
return _context.abrupt('return', (0, _assign2.default)({ sourceCode: sourceCode }, info));
case 36:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
function getHashForPath(_x2) {
return _ref.apply(this, arguments);
}
return getHashForPath;
}()
/**
* 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 _ref3 = (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);
}));
function save(_x3) {
return _ref3.apply(this, arguments);
}
return save;
}()
}, {
key: 'calculateHashForFile',
value: function () {
var _ref4 = (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);
}));
function calculateHashForFile(_x4) {
return _ref4.apply(this, arguments);
}
return calculateHashForFile;
}()
}, {
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 (0, _assign2.default)({ binaryData: binaryData }, info);
} else {
return (0, _assign2.default)({ 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 ? arguments[2] : true;
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 _ref5 = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(file, appRoot) {
var failOnCacheMiss = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
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);
}));
function loadFromFile(_x7, _x8) {
return _ref5.apply(this, arguments);
}
return loadFromFile;
}()
}, {
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|bower_components)[\\\/]/i) || filePath.match(/(atom|electron)\.asar/));
}
/**
* Returns whether a file has an inline source map
*
* @private
*/
}, {
key: 'hasSourceMap',
value: function hasSourceMap(sourceCode) {
var trimmed = sourceCode.trim();
return trimmed.lastIndexOf('//# sourceMap') > trimmed.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 = encodings.find(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 spaceCount = 0;
var threshold = 2;
if (str.length > 64) threshold = 4;
if (str.length > 512) threshold = 8;
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
if (c === 65536 || c < 8) controlCount++;
if (c > 14 && c < 32) controlCount++;
if (c === 32) spaceCount++;
if (controlCount > threshold) return true;
}
if (spaceCount < threshold) return true;
if (controlCount === 0) return false;
return controlCount / str.length < 0.02;
}
}]);
return FileChangedCache;
}();
exports.default = FileChangedCache;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9maWxlLWNoYW5nZS1jYWNoZS5qcyJdLCJuYW1lcyI6WyJkIiwicmVxdWlyZSIsIkZpbGVDaGFuZ2VkQ2FjaGUiLCJhcHBSb290IiwiZmFpbE9uQ2FjaGVNaXNzIiwiY2hhbmdlQ2FjaGUiLCJhYnNvbHV0ZUZpbGVQYXRoIiwiY2FjaGVLZXkiLCJyZXBsYWNlIiwib3JpZ2luYWxBcHBSb290IiwiY2FjaGVFbnRyeSIsIkVycm9yIiwiaW5mbyIsInN0YXQiLCJjdGltZSIsImdldFRpbWUiLCJzaXplIiwiaXNGaWxlIiwiY2FsY3VsYXRlSGFzaEZvckZpbGUiLCJkaWdlc3QiLCJzb3VyY2VDb2RlIiwiYmluYXJ5RGF0YSIsImhhc2giLCJpc01pbmlmaWVkIiwiY29udGVudHNBcmVNaW5pZmllZCIsImlzSW5Ob2RlTW9kdWxlcyIsImhhc1NvdXJjZU1hcCIsImlzRmlsZUJpbmFyeSIsImZpbGVQYXRoIiwidG9TYXZlIiwiZ2V0U2F2ZWREYXRhIiwiZ3ppcCIsIkJ1ZmZlciIsImJ1ZiIsIndyaXRlRmlsZSIsInJlYWRGaWxlIiwiZW5jb2RpbmciLCJkZXRlY3RGaWxlRW5jb2RpbmciLCJjcmVhdGVIYXNoIiwidXBkYXRlIiwicmVhbEFwcFJvb3QiLCJzdGF0U3luYyIsImNhbGN1bGF0ZUhhc2hGb3JGaWxlU3luYyIsImd6aXBTeW5jIiwid3JpdGVGaWxlU3luYyIsInJlYWRGaWxlU3luYyIsImRhdGEiLCJyZXQiLCJmaWxlIiwiSlNPTiIsImd1bnppcCIsInBhcnNlIiwibG9hZEZyb21EYXRhIiwic291cmNlIiwibGVuZ3RoIiwibmV3bGluZUNvdW50IiwiaSIsImF2Z0xpbmVMZW5ndGgiLCJtYXRjaCIsInRyaW1tZWQiLCJ0cmltIiwibGFzdEluZGV4T2YiLCJidWZmZXIiLCJzbGljZSIsImVuY29kaW5ncyIsImZpbmQiLCJ4IiwiY29udGFpbnNDb250cm9sQ2hhcmFjdGVycyIsInRvU3RyaW5nIiwic3RyIiwiY29udHJvbENvdW50Iiwic3BhY2VD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LCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgZnMgZnJvbSAnZnMnO1xuaW1wb3J0IHpsaWIgZnJvbSAnemxpYic7XG5pbXBvcnQgY3J5cHRvIGZyb20gJ2NyeXB0byc7XG5pbXBvcnQge3BmcywgcHpsaWJ9IGZyb20gJy4vcHJvbWlzZSc7XG5pbXBvcnQgc2FuaXRpemVGaWxlUGF0aCBmcm9tICcuL3Nhbml0aXplLXBhdGhzJztcblxuY29uc3QgZCA9IHJlcXVpcmUoJ2RlYnVnLWVsZWN0cm9uJykoJ2VsZWN0cm9uLWNvbXBpbGU6ZmlsZS1jaGFuZ2UtY2FjaGUnKTtcblxuLyoqXG4gKiBUaGlzIGNsYXNzIGNhY2hlcyBpbmZvcm1hdGlvbiBhYm91dCBmaWxlcyBhbmQgZGV0ZXJtaW5lcyB3aGV0aGVyIHRoZXkgaGF2ZVxuICogY2hhbmdlZCBjb250ZW50cyBvciBub3QuIE1vc3QgaW1wb3J0YW50bHksIHRoaXMgY2xhc3MgY2FjaGVzIHRoZSBoYXNoIG9mIHNlZW5cbiAqIGZpbGVzIHNvIHRoYXQgYXQgZGV2ZWxvcG1lbnQgdGltZSwgd2UgZG9uJ3QgaGF2ZSB0byByZWNhbGN1bGF0ZSB0aGVtIGNvbnN0YW50bHkuXG4gKlxuICogVGhpcyBjbGFzcyBpcyBhbHNvIHRoZSBjb3JlIG9mIGhvdyBlbGVjdHJvbi1jb21waWxlIHJ1bnMgcXVpY2tseSBpbiBwcm9kdWN0aW9uXG4gKiBtb2RlIC0gYWZ0ZXIgcHJlY29tcGlsYXRpb24sIHRoZSBjYWNoZSBpcyBzZXJpYWxpemVkIGFsb25nIHdpdGggdGhlIHJlc3Qgb2YgdGhlXG4gKiBkYXRhIGluIHtAbGluayBDb21waWxlckhvc3R9LCBzbyB0aGF0IHdoZW4gd2UgbG9hZCB0aGUgYXBwIGluIHByb2R1Y3Rpb24gbW9kZSxcbiAqIHdlIGRvbid0IGVuZCB1cCBjYWxjdWxhdGluZyBoYXNoZXMgb2YgZmlsZSBjb250ZW50IGF0IGFsbCwgb25seSB1c2luZyB0aGUgY29udGVudHNcbiAqIG9mIHRoaXMgY2FjaGUuXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEZpbGVDaGFuZ2VkQ2FjaGUge1xuICBjb25zdHJ1Y3RvcihhcHBSb290LCBmYWlsT25DYWNoZU1pc3M9ZmFsc2UpIHtcbiAgICB0aGlzLmFwcFJvb3QgPSBzYW5pdGl6ZUZpbGVQYXRoKGFwcFJvb3QpO1xuXG4gICAgdGhpcy5mYWlsT25DYWNoZU1pc3MgPSBmYWlsT25DYWNoZU1pc3M7XG4gICAgdGhpcy5jaGFuZ2VDYWNoZSA9IHt9O1xuICB9XG5cbiAgLyoqXG4gICAqIEFsbG93cyB5b3UgdG8gY3JlYXRlIGEgRmlsZUNoYW5nZWRDYWNoZSBmcm9tIHNlcmlhbGl6ZWQgZGF0YSBzYXZlZCBmcm9tXG4gICAqIHtAbGluayBnZXRTYXZlZERhdGF9LlxuICAgKlxuICAgKiBAcGFyYW0gIHtPYmplY3R9IGRhdGEgIFNhdmVkIGRhdGEgZnJvbSBnZXRTYXZlZERhdGEuXG4gICAqXG4gICAqIEBwYXJhbSAge3N0cmluZ30gYXBwUm9vdCAgVGhlIHRvcC1sZXZlbCBkaXJlY3RvcnkgZm9yIHlvdXIgYXBwbGljYXRpb24gKGkuZS5cbiAgICogICAgICAgICAgICAgICAgICAgICAgICAgICB0aGUgb25lIHdoaWNoIGhhcyB5b3VyIHBhY2thZ2UuanNvbikuXG4gICAqXG4gICAqIEBwYXJhbSAge2Jvb2xlYW59IGZhaWxPbkNhY2hlTWlzcyAob3B0aW9uYWwpICBJZiBUcnVlLCBjYWNoZSBtaXNzZXMgd2lsbCB0aHJvdy5cbiAgICpcbiAgICogQHJldHVybiB7RmlsZUNoYW5nZWRDYWNoZX1cbiAgICovXG4gIHN0YXRpYyBsb2FkRnJvbURhdGEoZGF0YSwgYXBwUm9vdCwgZmFpbE9uQ2FjaGVNaXNzPXRydWUpIHtcbiAgICBsZXQgcmV0ID0gbmV3IEZpbGVDaGFuZ2VkQ2FjaGUoYXBwUm9vdCwgZmFpbE9uQ2FjaGVNaXNzKTtcbiAgICByZXQuY2hhbmdlQ2FjaGUgPSBkYXRhLmNoYW5nZUNhY2hlO1xuICAgIHJldC5vcmlnaW5hbEFwcFJvb3QgPSBkYXRhLmFwcFJvb3Q7XG5cbiAgICByZXR1cm4gcmV0O1xuICB9XG5cblxuICAvKipcbiAgICogQWxsb3dzIHlvdSB0byBjcmVhdGUgYSBGaWxlQ2hhbmdlZENhY2hlIGZyb20gc2VyaWFsaXplZCBkYXRhIHNhdmVkIGZyb21cbiAgICoge0BsaW5rIHNhdmV9LlxuICAgKlxuICAgKiBAcGFyYW0gIHtzdHJpbmd9IGZpbGUgIFNhdmVkIGRhdGEgZnJvbSBzYXZlLlxuICAgKlxuICAgKiBAcGFyYW0gIHtzdHJpbmd9IGFwcFJvb3QgIFRoZSB0b3AtbGV2ZWwgZGlyZWN0b3J5IGZvciB5b3VyIGFwcGxpY2F0aW9uIChpLmUuXG4gICAqICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhlIG9uZSB3aGljaCBoYXMgeW91ciBwYWNrYWdlLmpzb24pLlxuICAgKlxuICAgKiBAcGFyYW0gIHtib29sZWFufSBmYWlsT25DYWNoZU1pc3MgKG9wdGlvbmFsKSAgSWYgVHJ1ZSwgY2FjaGUgbWlzc2VzIHdpbGwgdGhyb3cuXG4gICAqXG4gICAqIEByZXR1cm4ge1Byb21pc2U8RmlsZUNoYW5nZWRDYWNoZT59XG4gICAqL1xuICBzdGF0aWMgYXN5bmMgbG9hZEZyb21GaWxlKGZpbGUsIGFwcFJvb3QsIGZhaWxPbkNhY2hlTWlzcz10cnVlKSB7XG4gICAgZChgTG9hZGluZyBjYW5uZWQgRmlsZUNoYW5nZWRDYWNoZSBmcm9tICR7ZmlsZX1gKTtcblxuICAgIGxldCBidWYgPSBhd2FpdCBwZnMucmVhZEZpbGUoZmlsZSk7XG4gICAgcmV0dXJuIEZpbGVDaGFuZ2VkQ2FjaGUubG9hZEZyb21EYXRhKEpTT04ucGFyc2UoYXdhaXQgcHpsaWIuZ3VuemlwKGJ1ZikpLCBhcHBSb290LCBmYWlsT25DYWNoZU1pc3MpO1xuICB9XG5cblxuICAvKipcbiAgICogUmV0dXJucyBpbmZvcm1hdGlvbiBhYm91dCBhIGdpdmVuIGZpbGUsIGluY2x1ZGluZyBpdHMgaGFzaC4gVGhpcyBtZXRob2QgaXNcbiAgICogdGhlIG1haW4gbWV0aG9kIGZvciB0aGlzIGNhY2hlLlxuICAgKlxuICAgKiBAcGFyYW0gIHtzdHJpbmd9IGFic29sdXRlRmlsZVBhdGggIFRoZSBwYXRoIHRvIGEgZmlsZSB0byByZXRyaWV2ZSBpbmZvIG9uLlxuICAgKlxuICAgKiBAcmV0dXJuIHtQcm9taXNlPE9iamVjdD59XG4gICAqXG4gICAqIEBwcm9wZXJ0eSB7c3RyaW5nfSBoYXNoICBUaGUgU0hBMSBoYXNoIG9mIHRoZSBmaWxlXG4gICAqIEBwcm9wZXJ0eSB7Ym9vbGVhbn0gaXNNaW5pZmllZCAgVHJ1ZSBpZiB0aGUgZmlsZSBpcyBtaW5pZmllZFxuICAgKiBAcHJvcGVydHkge2Jvb2xlYW59IGlzSW5Ob2RlTW9kdWxlcyAgVHJ1ZSBpZiB0aGUgZmlsZSBpcyBpbiBhIGxpYnJhcnkgZGlyZWN0b3J5XG4gICAqIEBwcm9wZXJ0eSB7Ym9vbGVhbn0gaGFzU291cmNlTWFwICBUcnVlIGlmIHRoZSBmaWxlIGhhcyBhIHNvdXJjZSBtYXBcbiAgICogQHByb3BlcnR5IHtib29sZWFufSBpc0ZpbGVCaW5hcnkgIFRydWUgaWYgdGhlIGZpbGUgaXMgbm90IGEgdGV4dCBmaWxlXG4gICAqIEBwcm9wZXJ0eSB7QnVmZmVyfSBiaW5hcnlEYXRhIChvcHRpb25hbCkgIFRoZSBidWZmZXIgdGhhdCB3YXMgcmVhZCBpZiB0aGUgZmlsZVxuICAgKiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3YXMgYmluYXJ5IGFuZCB0aGVyZSB3YXMgYSBjYWNoZSBtaXNzLlxuICAgKiBAcHJvcGVydHkge3N0cmluZ30gY29kZSAob3B0aW9uYWwpICBUaGUgc3RyaW5nIHRoYXQgd2FzIHJlYWQgaWYgdGhlIGZpbGVcbiAgICogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2FzIHRleHQgYW5kIHRoZXJlIHdhcyBhIGNhY2hlIG1pc3NcbiAgICovXG4gIGFzeW5jIGdldEhhc2hGb3JQYXRoKGFic29sdXRlRmlsZVBhdGgpIHtcbiAgICBsZXQgY2FjaGVLZXkgPSBzYW5pdGl6ZUZpbGVQYXRoKGFic29sdXRlRmlsZVBhdGgpO1xuICAgIGlmICh0aGlzLmFwcFJvb3QpIHtcbiAgICAgIGNhY2hlS2V5ID0gY2FjaGVLZXkucmVwbGFjZSh0aGlzLmFwcFJvb3QsICcnKTtcbiAgICB9XG5cbiAgICAvLyBOQjogV2UgZG8gdGhpcyBiZWNhdXNlIHgtcmVxdWlyZSB3aWxsIGluY2x1ZGUgYW4gYWJzb2x1dGUgcGF0aCBmcm9tIHRoZVxuICAgIC8vIG9yaWdpbmFsIGJ1aWx0IGFwcCBhbmQgd2UgbmVlZCB0byBzdGlsbCBncm9rIGl0XG4gICAgaWYgKHRoaXMub3JpZ2luYWxBcHBSb290KSB7XG4gICAgICBjYWNoZUtleSA9IGNhY2hlS2V5LnJlcGxhY2UodGhpcy5vcmlnaW5hbEFwcFJvb3QsICcnKTtcbiAgICB9XG5cbiAgICBsZXQgY2FjaGVFbnRyeSA9IHRoaXMuY2hhbmdlQ2FjaGVbY2FjaGVLZXldO1xuXG4gICAgaWYgKHRoaXMuZmFpbE9uQ2FjaGVNaXNzKSB7XG4gICAgICBpZiAoIWNhY2hlRW50cnkpIHtcbiAgICAgICAgZChgVHJpZWQgdG8gcmVhZCBmaWxlIGNhY2hlIGVudHJ5IGZvciAke2Fic29sdXRlRmlsZVBhdGh9YCk7XG4gICAgICAgIGQoYGNhY2hlS2V5OiAke2NhY2hlS2V5fSwgYXBwUm9vdDogJHt0aGlzLmFwcFJvb3R9LCBvcmlnaW5hbEFwcFJvb3Q6ICR7dGhpcy5vcmlnaW5hbEFwcFJvb3R9YCk7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgQXNrZWQgZm9yICR7YWJzb2x1dGVGaWxlUGF0aH0gYnV0IGl0IHdhcyBub3QgcHJlY29tcGlsZWQhYCk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBjYWNoZUVudHJ5LmluZm87XG4gICAgfVxuXG4gICAgbGV0IHN0YXQgPSBhd2FpdCBwZnMuc3RhdChhYnNvbHV0ZUZpbGVQYXRoKTtcbiAgICBsZXQgY3RpbWUgPSBzdGF0LmN0aW1lLmdldFRpbWUoKTtcbiAgICBsZXQgc2l6ZSA9IHN0YXQuc2l6ZTtcbiAgICBpZiAoIXN0YXQgfHwgIXN0YXQuaXNGaWxlKCkpIHRocm93IG5ldyBFcnJvcihgQ2FuJ3Qgc3RhdCAke2Fic29sdXRlRmlsZVBhdGh9YCk7XG5cbiAgICBpZiAoY2FjaGVFbnRyeSkge1xuICAgICAgaWYgKGNhY2hlRW50cnkuY3RpbWUgPj0gY3RpbWUgJiYgY2FjaGVFbnRyeS5zaXplID09PSBzaXplKSB7XG4gICAgICAgIHJldHVybiBjYWNoZUVudHJ5LmluZm87XG4gICAgICB9XG5cbiAgICAgIGQoYEludmFsaWRhdGluZyBjYWNoZSBlbnRyeTogJHtjYWNoZUVudHJ5LmN0aW1lfSA9PT0gJHtjdGltZX0gJiYgJHtjYWNoZUVudHJ5LnNpemV9ID09PSAke3NpemV9YCk7XG4gICAgICBkZWxldGUgdGhpcy5jaGFuZ2VDYWNoZS5jYWNoZUVudHJ5O1xuICAgIH1cblxuICAgIGxldCB7ZGlnZXN0LCBzb3VyY2VDb2RlLCBiaW5hcnlEYXRhfSA9IGF3YWl0IHRoaXMuY2FsY3VsYXRlSGFzaEZvckZpbGUoYWJzb2x1dGVGaWxlUGF0aCk7XG5cbiAgICBsZXQgaW5mbyA9IHtcbiAgICAgIGhhc2g6IGRpZ2VzdCxcbiAgICAgIGlzTWluaWZpZWQ6IEZpbGVDaGFuZ2VkQ2FjaGUuY29udGVudHNBcmVNaW5pZmllZChzb3VyY2VDb2RlIHx8ICcnKSxcbiAgICAgIGlzSW5Ob2RlTW9kdWxlczogRmlsZUNoYW5nZWRDYWNoZS5pc0luTm9kZU1vZHVsZXMoYWJzb2x1dGVGaWxlUGF0aCksXG4gICAgICBoYXNTb3VyY2VNYXA6IEZpbGVDaGFuZ2VkQ2FjaGUuaGFzU291cmNlTWFwKHNvdXJjZUNvZGUgfHwgJycpLFxuICAgICAgaXNGaWxlQmluYXJ5OiAhIWJpbmFyeURhdGFcbiAgICB9O1xuXG4gICAgdGhpcy5jaGFuZ2VDYWNoZVtjYWNoZUtleV0gPSB7IGN0aW1lLCBzaXplLCBpbmZvIH07XG4gICAgZChgQ2FjaGUgZW50cnkgZm9yICR7Y2FjaGVLZXl9OiAke0pTT04uc3RyaW5naWZ5KHRoaXMuY2hhbmdlQ2FjaGVbY2FjaGVLZXldKX1gKTtcblxuICAgIGlmIChiaW5hcnlEYXRhKSB7XG4gICAgICByZXR1cm4gT2JqZWN0LmFzc2lnbih7YmluYXJ5RGF0YX0sIGluZm8pO1xuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gT2JqZWN0LmFzc2lnbih7c291cmNlQ29kZX0sIGluZm8pO1xuICAgIH1cbiAgfVxuXG5cbiAgLyoqXG4gICAqIFJldHVybnMgZGF0YSB0aGF0IGNhbiBwYXNzZWQgdG8ge0BsaW5rIGxvYWRGcm9tRGF0YX0gdG8gcmVoeWRyYXRlIHRoaXMgY2FjaGUuXG4gICAqXG4gICAqIEByZXR1cm4ge09iamVjdH1cbiAgICovXG4gIGdldFNhdmVkRGF0YSgpIHtcbiAgICByZXR1cm4geyBjaGFuZ2VDYWNoZTogdGhpcy5jaGFuZ2VDYWNoZSwgYXBwUm9vdDogdGhpcy5hcHBSb290IH07XG4gIH1cblxuICAvKipcbiAgICogU2VyaWFsaXplcyB0aGlzIG9iamVjdCdzIGRhdGEgdG8gYSBmaWxlLlxuICAgKlxuICAgKiBAcGFyYW0ge3N0cmluZ30gZmlsZVBhdGggIFRoZSBwYXRoIHRvIHNhdmUgZGF0YSB0by5cbiAgICpcbiAgICogQHJldHVybiB7UHJvbWlzZX0gQ29tcGxldGlvbi5cbiAgICovXG4gIGFzeW5jIHNhdmUoZmlsZVBhdGgpIHtcbiAgICBsZXQgdG9TYXZlID0gdGhpcy5nZXRTYXZlZERhdGEoKTtcblxuICAgIGxldCBidWYgPSBhd2FpdCBwemxpYi5nemlwKG5ldyBCdWZmZXIoSlNPTi5zdHJpbmdpZnkodG9TYXZlKSkpO1xuICAgIGF3YWl0IHBmcy53cml0ZUZpbGUoZmlsZVBhdGgsIGJ1Zik7XG4gIH1cblxuICBhc3luYyBjYWxjdWxhdGVIYXNoRm9yRmlsZShhYnNvbHV0ZUZpbGVQYXRoKSB7XG4gICAgbGV0IGJ1ZiA9IGF3YWl0IHBmcy5yZWFkRmlsZShhYnNvbHV0ZUZpbGVQYXRoKTtcbiAgICBsZXQgZW5jb2RpbmcgPSBGaWxlQ2hhbmdlZENhY2hlLmRldGVjdEZpbGVFbmNvZGluZyhidWYpO1xuXG4gICAgaWYgKCFlbmNvZGluZykge1xuICAgICAgbGV0IGRpZ2VzdCA9IGNyeXB0by5jcmVhdGVIYXNoKCdzaGExJykudXBkYXRlKGJ1ZikuZGlnZXN0KCdoZXgnKTtcbiAgICAgIHJldHVybiB7IHNvdXJjZUNvZGU6IG51bGwsIGRpZ2VzdCwgYmluYXJ5RGF0YTogYnVmIH07XG4gICAgfVxuXG4gICAgbGV0IHNvdXJjZUNvZGUgPSBhd2FpdCBwZnMucmVhZEZpbGUoYWJzb2x1dGVGaWxlUGF0aCwgZW5jb2RpbmcpO1xuICAgIGxldCBkaWdlc3QgPSBjcnlwdG8uY3JlYXRlSGFzaCgnc2hhMScpLnVwZGF0ZShzb3VyY2VDb2RlLCAndXRmOCcpLmRpZ2VzdCgnaGV4Jyk7XG5cbiAgICByZXR1cm4ge3NvdXJjZUNvZGUsIGRpZ2VzdCwgYmluYXJ5RGF0YTogbnVsbCB9O1xuICB9XG5cbiAgZ2V0SGFzaEZvclBhdGhTeW5jKGFic29sdXRlRmlsZVBhdGgpIHtcbiAgICBsZXQgY2FjaGVLZXkgPSBzYW5pdGl6ZUZpbGVQYXRoKGFic29sdXRlRmlsZVBhdGgpO1xuICAgIGlmICh0aGlzLmFwcFJvb3QpIHtcbiAgICAgIGNhY2hlS2V5ID0gY2FjaGVLZXkucmVwbGFjZSh0aGlzLmFwcFJvb3QsICcnKTtcbiAgICB9XG5cbiAgICAvLyBOQjogV2UgZG8gdGhpcyBiZWNhdXNlIHgtcmVxdWlyZSB3aWxsIGluY2x1ZGUgYW4gYWJzb2x1dGUgcGF0aCBmcm9tIHRoZVxuICAgIC8vIG9yaWdpbmFsIGJ1aWx0IGFwcCBhbmQgd2UgbmVlZCB0byBzdGlsbCBncm9rIGl0XG4gICAgaWYgKHRoaXMub3JpZ2luYWxBcHBSb290KSB7XG4gICAgICBjYWNoZUtleSA9IGNhY2hlS2V5LnJlcGxhY2UodGhpcy5vcmlnaW5hbEFwcFJvb3QsICcnKTtcbiAgICB9XG5cbiAgICBpZiAodGhpcy5yZWFsQXBwUm9vdCkge1xuICAgICAgY2FjaGVLZXkgPSBjYWNoZUtleS5yZXBsYWNlKHRoaXMucmVhbEFwcFJvb3QsICcnKTtcbiAgICB9XG5cbiAgICBsZXQgY2FjaGVFbnRyeSA9IHRoaXMuY2hhbmdlQ2FjaGVbY2FjaGVLZXldO1xuXG4gICAgaWYgKHRoaXMuZmFpbE9uQ2FjaGVNaXNzKSB7XG4gICAgICBpZiAoIWNhY2hlRW50cnkpIHtcbiAgICAgICAgZChgVHJpZWQgdG8gcmVhZCBmaWxlIGNhY2hlIGVudHJ5IGZvciAke2Fic29sdXRlRmlsZVBhdGh9YCk7XG4gICAgICAgIGQoYGNhY2hlS2V5OiAke2NhY2hlS2V5fSwgYXBwUm9vdDogJHt0aGlzLmFwcFJvb3R9LCBvcmlnaW5hbEFwcFJvb3Q6ICR7dGhpcy5vcmlnaW5hbEFwcFJvb3R9YCk7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgQXNrZWQgZm9yICR7YWJzb2x1dGVGaWxlUGF0aH0gYnV0IGl0IHdhcyBub3QgcHJlY29tcGlsZWQhYCk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBjYWNoZUVudHJ5LmluZm87XG4gICAgfVxuXG4gICAgbGV0IHN0YXQgPSBmcy5zdGF0U3luYyhhYnNvbHV0ZUZpbGVQYXRoKTtcbiAgICBsZXQgY3RpbWUgPSBzdGF0LmN0aW1lLmdldFRpbWUoKTtcbiAgICBsZXQgc2l6ZSA9IHN0YXQuc2l6ZTtcbiAgICBpZiAoIXN0YXQgfHwgIXN0YXQuaXNGaWxlKCkpIHRocm93IG5ldyBFcnJvcihgQ2FuJ3Qgc3RhdCAke2Fic29sdXRlRmlsZVBhdGh9YCk7XG5cbiAgICBpZiAoY2FjaGVFbnRyeSkge1xuICAgICAgaWYgKGNhY2hlRW50cnkuY3RpbWUgPj0gY3RpbWUgJiYgY2FjaGVFbnRyeS5zaXplID09PSBzaXplKSB7XG4gICAgICAgIHJldHVybiBjYWNoZUVudHJ5LmluZm87XG4gICAgICB9XG5cbiAgICAgIGQoYEludmFsaWRhdGluZyBjYWNoZSBlbnRyeTogJHtjYWNoZUVudHJ5LmN0aW1lfSA9PT0gJHtjdGltZX0gJiYgJHtjYWNoZUVudHJ5LnNpemV9ID09PSAke3NpemV9YCk7XG4gICAgICBkZWxldGUgdGhpcy5jaGFuZ2VDYWNoZS5jYWNoZUVudHJ5O1xuICAgIH1cblxuICAgIGxldCB7ZGlnZXN0LCBzb3VyY2VDb2RlLCBiaW5hcnlEYXRhfSA9IHRoaXMuY2FsY3VsYXRlSGFzaEZvckZpbGVTeW5jKGFic29sdXRlRmlsZVBhdGgpO1xuXG4gICAgbGV0IGluZm8gPSB7XG4gICAgICBoYXNoOiBkaWdlc3QsXG4gICAgICBpc01pbmlmaWVkOiBGaWxlQ2hhbmdlZENhY2hlLmNvbnRlbnRzQXJlTWluaWZpZWQoc291cmNlQ29kZSB8fCAnJyksXG4gICAgICBpc0luTm9kZU1vZHVsZXM6IEZpbGVDaGFuZ2VkQ2FjaGUuaXNJbk5vZGVNb2R1bGVzKGFic29sdXRlRmlsZVBhdGgpLFxuICAgICAgaGFzU291cmNlTWFwOiBGaWxlQ2hhbmdlZENhY2hlLmhhc1NvdXJjZU1hcChzb3VyY2VDb2RlIHx8ICcnKSxcbiAgICAgIGlzRmlsZUJpbmFyeTogISFiaW5hcnlEYXRhXG4gICAgfTtcblxuICAgIHRoaXMuY2hhbmdlQ2FjaGVbY2FjaGVLZXldID0geyBjdGltZSwgc2l6ZSwgaW5mbyB9O1xuICAgIGQoYENhY2hlIGVudHJ5IGZvciAke2NhY2hlS2V5fTogJHtKU09OLnN0cmluZ2lmeSh0aGlzLmNoYW5nZUNhY2hlW2NhY2hlS2V5XSl9YCk7XG5cbiAgICBpZiAoYmluYXJ5RGF0YSkge1xuICAgICAgcmV0dXJuIE9iamVjdC5hc3NpZ24oe2JpbmFyeURhdGF9LCBpbmZvKTtcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIE9iamVjdC5hc3NpZ24oe3NvdXJjZUNvZGV9LCBpbmZvKTtcbiAgICB9XG4gIH1cblxuICBzYXZlU3luYyhmaWxlUGF0aCkge1xuICAgIGxldCB0b1NhdmUgPSB0aGlzLmdldFNhdmVkRGF0YSgpO1xuXG4gICAgbGV0IGJ1ZiA9IHpsaWIuZ3ppcFN5bmMobmV3IEJ1ZmZlcihKU09OLnN0cmluZ2lmeSh0b1NhdmUpKSk7XG4gICAgZnMud3JpdGVGaWxlU3luYyhmaWxlUGF0aCwgYnVmKTtcbiAgfVxuXG4gIGNhbGN1bGF0ZUhhc2hGb3JGaWxlU3luYyhhYnNvbHV0ZUZpbGVQYXRoKSB7XG4gICAgbGV0IGJ1ZiA9IGZzLnJlYWRGaWxlU3luYyhhYnNvbHV0ZUZpbGVQYXRoKTtcbiAgICBsZXQgZW5jb2RpbmcgPSBGaWxlQ2hhbmdlZENhY2hlLmRldGVjdEZpbGVFbmNvZGluZyhidWYpO1xuXG4gICAgaWYgKCFlbmNvZGluZykge1xuICAgICAgbGV0IGRpZ2VzdCA9IGNyeXB0by5jcmVhdGVIYXNoKCdzaGExJykudXBkYXRlKGJ1ZikuZGlnZXN0KCdoZXgnKTtcbiAgICAgIHJldHVybiB7IHNvdXJjZUNvZGU6IG51bGwsIGRpZ2VzdCwgYmluYXJ5RGF0YTogYnVmfTtcbiAgICB9XG5cbiAgICBsZXQgc291cmNlQ29kZSA9IGZzLnJlYWRGaWxlU3luYyhhYnNvbHV0ZUZpbGVQYXRoLCBlbmNvZGluZyk7XG4gICAgbGV0IGRpZ2VzdCA9IGNyeXB0by5jcmVhdGVIYXNoKCdzaGExJykudXBkYXRlKHNvdXJjZUNvZGUsICd1dGY4JykuZGlnZXN0KCdoZXgnKTtcblxuICAgIHJldHVybiB7c291cmNlQ29kZSwgZGlnZXN0LCBiaW5hcnlEYXRhOiBudWxsfTtcbiAgfVxuXG5cbiAgLyoqXG4gICAqIERldGVybWluZXMgdmlhIHNvbWUgc3RhdGlzdGljcyB3aGV0aGVyIGEgZmlsZSBpcyBsaWtlbHkgdG8gYmUgbWluaWZpZWQuXG4gICAqXG4gICAqIEBwcml2YXRlXG4gICAqL1xuICBzdGF0aWMgY29udGVudHNBcmVNaW5pZmllZChzb3VyY2UpIHtcbiAgICBsZXQgbGVuZ3RoID0gc291cmNlLmxlbmd0aDtcbiAgICBpZiAobGVuZ3RoID4gMTAyNCkgbGVuZ3RoID0gMTAyNDtcblxuICAgIGxldCBuZXdsaW5lQ291bnQgPSAwO1xuXG4gICAgLy8gUm9sbCB0aHJvdWdoIHRoZSBjaGFyYWN0ZXJzIGFuZCBkZXRlcm1pbmUgdGhlIGF2ZXJhZ2UgbGluZSBsZW5ndGhcbiAgICBmb3IobGV0IGk9MDsgaSA8IHNvdXJjZS5sZW5ndGg7IGkrKykge1xuICAgICAgaWYgKHNvdXJjZVtpXSA9PT0gJ1xcbicpIG5ld2xpbmVDb3VudCsrO1xuICAgIH1cblxuICAgIC8vIE5vIE5ld2xpbmVzPyBBbnkgZmlsZSBvdGhlciB0aGFuIGEgc3VwZXIgc21hbGwgb25lIGlzIG1pbmlmaWVkXG4gICAgaWYgKG5ld2xpbmVDb3VudCA9PT0gMCkge1xuICAgICAgcmV0dXJuIChsZW5ndGggPiA4MCk7XG4gICAgfVxuXG4gICAgbGV0IGF2Z0xpbmVMZW5ndGggPSBsZW5ndGggLyBuZXdsaW5lQ291bnQ7XG4gICAgcmV0dXJuIChhdmdMaW5lTGVuZ3RoID4gODApO1xuICB9XG5cblxuICAvKipcbiAgICogRGV0ZXJtaW5lcyB3aGV0aGVyIGEgcGF0aCBpcyBpbiBub2RlX21vZHVsZXMgb3IgdGhlIEVsZWN0cm9uIGluaXQgY29kZVxuICAgKlxuICAgKiBAcHJpdmF0ZVxuICAgKi9cbiAgc3RhdGljIGlzSW5Ob2RlTW9kdWxlcyhmaWxlUGF0aCkge1xuICAgIHJldHVybiAhIShmaWxlUGF0aC5tYXRjaCgvKG5vZGVfbW9kdWxlc3xib3dlcl9jb21wb25lbnRzKVtcXFxcXFwvXS9pKSB8fCBmaWxlUGF0aC5tYXRjaCgvKGF0b218ZWxlY3Ryb24pXFwuYXNhci8pKTtcbiAgfVxuXG5cbiAgLyoqXG4gICAqIFJldHVybnMgd2hldGhlciBhIGZpbGUgaGFzIGFuIGlubGluZSBzb3VyY2UgbWFwXG4gICAqXG4gICAqIEBwcml2YXRlXG4gICAqL1xuICBzdGF0aWMgaGFzU291cmNlTWFwKHNvdXJjZUNvZGUpIHtcbiAgICBjb25zdCB0cmltbWVkID0gc291cmNlQ29kZS50cmltKCk7XG4gICAgcmV0dXJuIHRyaW1tZWQubGFzdEluZGV4T2YoJy8vIyBzb3VyY2VNYXAnKSA+IHRyaW1tZWQubGFzdEluZGV4T2YoJ1xcbicpO1xuICB9XG5cbiAgLyoqXG4gICAqIERldGVybWluZXMgdGhlIGVuY29kaW5nIG9mIGEgZmlsZSBmcm9tIHRoZSB0d28gbW9zdCBjb21tb24gZW5jb2RpbmdzIGJ5IHRyeWluZ1xuICAgKiB0byBkZWNvZGUgaXQgdGhlbiBsb29raW5nIGZvciBlbmNvZGluZyBlcnJvcnNcbiAgICpcbiAgICogQHByaXZhdGVcbiAgICovXG4gIHN0YXRpYyBkZXRlY3RGaWxlRW5jb2RpbmcoYnVmZmVyKSB7XG4gICAgaWYgKGJ1ZmZlci5sZW5ndGggPCAxKSByZXR1cm4gZmFsc2U7XG4gICAgbGV0IGJ1ZiA9IChidWZmZXIubGVuZ3RoIDwgNDA5NiA/IGJ1ZmZlciA6IGJ1ZmZlci5zbGljZSgwLCA0MDk2KSk7XG5cbiAgICBjb25zdCBlbmNvZGluZ3MgPSBbJ3V0ZjgnLCAndXRmMTZsZSddO1xuXG4gICAgbGV0IGVuY29kaW5nID0gZW5jb2RpbmdzLmZpbmQoXG4gICAgICAoeCkgPT4gIUZpbGVDaGFuZ2VkQ2FjaGUuY29udGFpbnNDb250cm9sQ2hhcmFjdGVycyhidWYudG9TdHJpbmcoeCkpKTtcblxuICAgIHJldHVybiBlbmNvZGluZztcbiAgfVxuXG4gIC8qKlxuICAgKiBEZXRlcm1pbmVzIHdoZXRoZXIgYSBzdHJpbmcgaXMgbGlrZWx5IHRvIGJlIHBvb3JseSBlbmNvZGVkIGJ5IGxvb2tpbmcgZm9yXG4gICAqIGNvbnRyb2wgY2hhcmFjdGVycyBhYm92ZSBhIGNlcnRhaW4gdGhyZXNob2xkXG4gICAqXG4gICAqIEBwcml2YXRlXG4gICAqL1xuICBzdGF0aWMgY29udGFpbnNDb250cm9sQ2hhcmFjdGVycyhzdHIpIHtcbiAgICBsZXQgY29udHJvbENvdW50ID0gMDtcbiAgICBsZXQgc3BhY2VDb3VudCA9IDA7XG4gICAgbGV0IHRocmVzaG9sZCA9IDI7XG4gICAgaWYgKHN0ci5sZW5ndGggPiA2NCkgdGhyZXNob2xkID0gNDtcbiAgICBpZiAoc3RyLmxlbmd0aCA+IDUxMikgdGhyZXNob2xkID0gODtcblxuICAgIGZvciAobGV0IGk9MDsgaSA8IHN0ci5sZW5ndGg7IGkrKykge1xuICAgICAgbGV0IGMgPSBzdHIuY2hhckNvZGVBdChpKTtcbiAgICAgIGlmIChjID09PSA2NTUzNiB8fCBjIDwgOCkgY29udHJvbENvdW50Kys7XG4gICAgICBpZiAoYyA+IDE0ICYmIGMgPCAzMikgY29udHJvbENvdW50Kys7XG4gICAgICBpZiAoYyA9PT0gMzIpIHNwYWNlQ291bnQrKztcblxuICAgICAgaWYgKGNvbnRyb2xDb3VudCA+IHRocmVzaG9sZCkgcmV0dXJuIHRydWU7XG4gICAgfVxuXG4gICAgaWYgKHNwYWNlQ291bnQgPCB0aHJlc2hvbGQpIHJldHVybiB0cnVlO1xuXG4gICAgaWYgKGNvbnRyb2xDb3VudCA9PT0gMCkgcmV0dXJuIGZhbHNlO1xuICAgIHJldHVybiAoY29udHJvbENvdW50IC8gc3RyLmxlbmd0aCkgPCAwLjAyO1xuICB9XG59XG4iXX0=