UNPKG

electron-compile

Version:

Electron supporting package to compile JS and CSS in Electron applications

604 lines (477 loc) 46.7 kB
'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=