metaapi.cloud-sdk
Version:
SDK for MetaApi, a professional cloud forex API which includes MetaTrader REST API and MetaTrader websocket API. Supports both MetaTrader 5 (MT5) and MetaTrader 4 (MT4). CopyFactory copy trading API included. (https://metaapi.cloud)
223 lines (222 loc) • 30.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return PacketLogger;
}
});
const _fsextra = /*#__PURE__*/ _interop_require_default(require("fs-extra"));
const _moment = /*#__PURE__*/ _interop_require_default(require("moment"));
const _optionsValidator = /*#__PURE__*/ _interop_require_default(require("../optionsValidator"));
const _logger = /*#__PURE__*/ _interop_require_default(require("../../logger"));
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
let PacketLogger = class PacketLogger {
_ensurePreviousPriceObject(accountId) {
if (!this._previousPrices[accountId]) {
this._previousPrices[accountId] = {};
}
}
/**
* Processes packets and pushes them into save queue
* @param {Object} packet packet to log
*/ // eslint-disable-next-line complexity
logPacket(packet) {
const instanceIndex = packet.instanceIndex || 0;
if (!this._writeQueue[packet.accountId]) {
this._writeQueue[packet.accountId] = {
isWriting: false,
queue: []
};
}
if (packet.type === "status") {
return;
}
if (!this._lastSNPacket[packet.accountId]) {
this._lastSNPacket[packet.accountId] = {};
}
if ([
"keepalive",
"noop"
].includes(packet.type)) {
this._lastSNPacket[packet.accountId][instanceIndex] = packet;
return;
}
const queue = this._writeQueue[packet.accountId].queue;
if (!this._previousPrices[packet.accountId]) {
this._previousPrices[packet.accountId] = {};
}
const prevPrice = this._previousPrices[packet.accountId][instanceIndex];
if (packet.type !== "prices") {
if (prevPrice) {
this._recordPrices(packet.accountId, instanceIndex);
}
if (packet.type === "specifications" && this._compressSpecifications) {
queue.push(JSON.stringify({
type: packet.type,
sequenceNumber: packet.sequenceNumber,
sequenceTimestamp: packet.sequenceTimestamp,
instanceIndex
}));
} else {
queue.push(JSON.stringify(packet));
}
} else {
if (!this._compressPrices) {
queue.push(JSON.stringify(packet));
} else {
if (prevPrice) {
const validSequenceNumbers = [
prevPrice.last.sequenceNumber,
prevPrice.last.sequenceNumber + 1
];
if (this._lastSNPacket[packet.accountId][instanceIndex]) {
validSequenceNumbers.push(this._lastSNPacket[packet.accountId][instanceIndex].sequenceNumber + 1);
}
if (!validSequenceNumbers.includes(packet.sequenceNumber)) {
this._recordPrices(packet.accountId, instanceIndex);
this._ensurePreviousPriceObject(packet.accountId);
this._previousPrices[packet.accountId][instanceIndex] = {
first: packet,
last: packet
};
queue.push(JSON.stringify(packet));
} else {
this._previousPrices[packet.accountId][instanceIndex].last = packet;
}
} else {
this._ensurePreviousPriceObject(packet.accountId);
this._previousPrices[packet.accountId][instanceIndex] = {
first: packet,
last: packet
};
queue.push(JSON.stringify(packet));
}
}
}
}
/**
* Returns log messages within date bounds as an array of objects
* @param {String} accountId account id
* @param {Date} dateAfter date to get logs after
* @param {Date} dateBefore date to get logs before
* @returns {Array<Object>} log messages
*/ async readLogs(accountId, dateAfter, dateBefore) {
const folders = await _fsextra.default.readdir(this._root);
const packets = [];
for (let folder of folders){
const filePath = `${this._root}/${folder}/${accountId}.log`;
if (await _fsextra.default.pathExists(filePath)) {
const contents = await _fsextra.default.readFile(filePath, "utf8");
let messages = contents.split("\r\n").filter((message)=>message.length).map((message)=>{
return {
date: new Date(message.slice(1, 24)),
message: message.slice(26)
};
});
if (dateAfter) {
messages = messages.filter((message)=>message.date > dateAfter);
}
if (dateBefore) {
messages = messages.filter((message)=>message.date < dateBefore);
}
packets.push(...messages);
}
}
return packets;
}
/**
* Returns path for account log file
* @param {String} accountId account id
* @returns {String} file path
*/ async getFilePath(accountId) {
const fileIndex = Math.floor(new Date().getHours() / this._logFileSizeInHours);
const folderName = `${(0, _moment.default)().format("YYYY-MM-DD")}-${fileIndex > 9 ? fileIndex : "0" + fileIndex}`;
await _fsextra.default.ensureDir(`${this._root}/${folderName}`);
return `${this._root}/${folderName}/${accountId}.log`;
}
/**
* Initializes the packet logger
*/ start() {
this._previousPrices = {};
if (!this._recordInteval) {
this._recordInteval = setInterval(()=>this._appendLogs(), 1000);
this._deleteOldLogsInterval = setInterval(()=>this._deleteOldData(), 10000);
}
}
/**
* Deinitializes the packet logger
*/ stop() {
clearInterval(this._recordInteval);
clearInterval(this._deleteOldLogsInterval);
}
/**
* Records price packet messages to log files
* @param {String} accountId account id
*/ _recordPrices(accountId, instanceNumber) {
const prevPrice = this._previousPrices[accountId][instanceNumber] || {
first: {},
last: {}
};
const queue = this._writeQueue[accountId].queue;
delete this._previousPrices[accountId][instanceNumber];
if (!Object.keys(this._previousPrices[accountId]).length) {
delete this._previousPrices[accountId];
}
if (prevPrice.first.sequenceNumber !== prevPrice.last.sequenceNumber) {
queue.push(JSON.stringify(prevPrice.last));
queue.push(`Recorded price packets ${prevPrice.first.sequenceNumber}` + `-${prevPrice.last.sequenceNumber}, instanceIndex: ${instanceNumber}`);
}
}
/**
* Writes logs to files
*/ async _appendLogs() {
Object.keys(this._writeQueue).forEach(async (accountId)=>{
const queue = this._writeQueue[accountId];
if (!queue.isWriting && queue.queue.length) {
queue.isWriting = true;
try {
const filePath = await this.getFilePath(accountId);
const writeString = queue.queue.reduce((a, b)=>a + `[${(0, _moment.default)().format("YYYY-MM-DD HH:mm:ss.SSS")}] ${b}\r\n`, "");
queue.queue = [];
await _fsextra.default.appendFile(filePath, writeString);
} catch (err) {
this._logger.error(`${accountId}: Failed to record packet log`, err);
}
queue.isWriting = false;
}
});
}
/**
* Deletes folders when the folder limit is exceeded
*/ async _deleteOldData() {
const contents = await _fsextra.default.readdir(this._root);
contents.reverse().slice(this._fileNumberLimit).forEach(async (folderName)=>{
await _fsextra.default.remove(`${this._root}/${folderName}`);
});
}
/**
* Constructs the class
* @param {PacketLoggerOpts} opts packet logger options
*/ constructor(opts){
const validator = new _optionsValidator.default();
opts = opts || {};
this._fileNumberLimit = validator.validateNonZero(opts.fileNumberLimit, 12, "packetLogger.fileNumberLimit");
this._logFileSizeInHours = validator.validateNonZero(opts.logFileSizeInHours, 4, "packetLogger.logFileSizeInHours");
this._compressSpecifications = validator.validateBoolean(opts.compressSpecifications, true, "packetLogger.compressSpecifications");
this._compressPrices = validator.validateBoolean(opts.compressPrices, true, "packetLogger.compressPrices");
this._previousPrices = {};
this._lastSNPacket = {};
this._writeQueue = {};
this._root = "./.metaapi/logs";
this._logger = _logger.default.getLogger("PacketLogger");
_fsextra.default.ensureDir(this._root);
}
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5pbXBvcnQgZnMgZnJvbSAnZnMtZXh0cmEnO1xuaW1wb3J0IG1vbWVudCBmcm9tICdtb21lbnQnO1xuaW1wb3J0IE9wdGlvbnNWYWxpZGF0b3IgZnJvbSAnLi4vb3B0aW9uc1ZhbGlkYXRvcic7XG5pbXBvcnQgTG9nZ2VyTWFuYWdlciBmcm9tICcuLi8uLi9sb2dnZXInO1xuXG4vKipcbiAqIFBhY2tldCBsb2dnZXIgb3B0aW9uc1xuICogQHR5cGVkZWYge09iamVjdH0gUGFja2V0TG9nZ2VyT3B0c1xuICogQHByb3BlcnR5IHtCb29sZWFufSBbZW5hYmxlZF0gd2hldGhlciBwYWNrZXQgbG9nZ2VyIGlzIGVuYWJsZWRcbiAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBbZmlsZU51bWJlckxpbWl0XSBtYXhpbXVtIGFtb3VudCBvZiBmaWxlcyBwZXIgYWNjb3VudCwgZGVmYXVsdCB2YWx1ZSBpcyAxMlxuICogQHByb3BlcnR5IHtOdW1iZXJ9IFtsb2dGaWxlU2l6ZUluSG91cnNdIGFtb3VudCBvZiBsb2dnZWQgaG91cnMgcGVyIGFjY291bnQgZmlsZSwgZGVmYXVsdCB2YWx1ZSBpcyA0XG4gKiBAcHJvcGVydHkge0Jvb2xlYW59IFtjb21wcmVzc1NwZWNpZmljYXRpb25zXSB3aGV0aGVyIHRvIGNvbXByZXNzIHNwZWNpZmljYXRpb25zIHBhY2tldHMsIGRlZmF1bHQgdmFsdWUgaXMgdHJ1ZVxuICogQHByb3BlcnR5IHtCb29sZWFufSBbY29tcHJlc3NQcmljZXNdIHdoZXRoZXIgdG8gY29tcHJlc3Mgc3BlY2lmaWNhdGlvbnMgcGFja2V0cywgZGVmYXVsdCB2YWx1ZSBpcyB0cnVlXG4gKi9cblxuLyoqXG4gKiBBIGNsYXNzIHdoaWNoIHJlY29yZHMgcGFja2V0cyBpbnRvIGxvZyBmaWxlc1xuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBQYWNrZXRMb2dnZXIge1xuXG4gIC8qKlxuICAgKiBDb25zdHJ1Y3RzIHRoZSBjbGFzc1xuICAgKiBAcGFyYW0ge1BhY2tldExvZ2dlck9wdHN9IG9wdHMgcGFja2V0IGxvZ2dlciBvcHRpb25zXG4gICAqL1xuICBjb25zdHJ1Y3RvcihvcHRzKSB7XG4gICAgY29uc3QgdmFsaWRhdG9yID0gbmV3IE9wdGlvbnNWYWxpZGF0b3IoKTtcbiAgICBvcHRzID0gb3B0cyB8fCB7fTtcbiAgICB0aGlzLl9maWxlTnVtYmVyTGltaXQgPSB2YWxpZGF0b3IudmFsaWRhdGVOb25aZXJvKG9wdHMuZmlsZU51bWJlckxpbWl0LCAxMiwgJ3BhY2tldExvZ2dlci5maWxlTnVtYmVyTGltaXQnKTtcbiAgICB0aGlzLl9sb2dGaWxlU2l6ZUluSG91cnMgPSB2YWxpZGF0b3IudmFsaWRhdGVOb25aZXJvKG9wdHMubG9nRmlsZVNpemVJbkhvdXJzLCA0LFxuICAgICAgJ3BhY2tldExvZ2dlci5sb2dGaWxlU2l6ZUluSG91cnMnKTtcbiAgICB0aGlzLl9jb21wcmVzc1NwZWNpZmljYXRpb25zID0gdmFsaWRhdG9yLnZhbGlkYXRlQm9vbGVhbihvcHRzLmNvbXByZXNzU3BlY2lmaWNhdGlvbnMsIHRydWUsXG4gICAgICAncGFja2V0TG9nZ2VyLmNvbXByZXNzU3BlY2lmaWNhdGlvbnMnKTtcbiAgICB0aGlzLl9jb21wcmVzc1ByaWNlcyA9IHZhbGlkYXRvci52YWxpZGF0ZUJvb2xlYW4ob3B0cy5jb21wcmVzc1ByaWNlcywgdHJ1ZSwgJ3BhY2tldExvZ2dlci5jb21wcmVzc1ByaWNlcycpO1xuICAgIHRoaXMuX3ByZXZpb3VzUHJpY2VzID0ge307XG4gICAgdGhpcy5fbGFzdFNOUGFja2V0ID0ge307XG4gICAgdGhpcy5fd3JpdGVRdWV1ZSA9IHt9O1xuICAgIHRoaXMuX3Jvb3QgPSAnLi8ubWV0YWFwaS9sb2dzJztcbiAgICB0aGlzLl9sb2dnZXIgPSBMb2dnZXJNYW5hZ2VyLmdldExvZ2dlcignUGFja2V0TG9nZ2VyJyk7XG4gICAgZnMuZW5zdXJlRGlyKHRoaXMuX3Jvb3QpO1xuICB9XG5cbiAgX2Vuc3VyZVByZXZpb3VzUHJpY2VPYmplY3QoYWNjb3VudElkKSB7XG4gICAgaWYoIXRoaXMuX3ByZXZpb3VzUHJpY2VzW2FjY291bnRJZF0pIHtcbiAgICAgIHRoaXMuX3ByZXZpb3VzUHJpY2VzW2FjY291bnRJZF0gPSB7fTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUHJvY2Vzc2VzIHBhY2tldHMgYW5kIHB1c2hlcyB0aGVtIGludG8gc2F2ZSBxdWV1ZVxuICAgKiBAcGFyYW0ge09iamVjdH0gcGFja2V0IHBhY2tldCB0byBsb2dcbiAgICovXG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBjb21wbGV4aXR5XG4gIGxvZ1BhY2tldChwYWNrZXQpIHtcbiAgICBjb25zdCBpbnN0YW5jZUluZGV4ID0gcGFja2V0Lmluc3RhbmNlSW5kZXggfHwgMDtcbiAgICBpZighdGhpcy5fd3JpdGVRdWV1ZVtwYWNrZXQuYWNjb3VudElkXSkge1xuICAgICAgdGhpcy5fd3JpdGVRdWV1ZVtwYWNrZXQuYWNjb3VudElkXSA9IHtpc1dyaXRpbmc6IGZhbHNlLCBxdWV1ZTogW119O1xuICAgIH1cbiAgICBpZihwYWNrZXQudHlwZSA9PT0gJ3N0YXR1cycpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYoIXRoaXMuX2xhc3RTTlBhY2tldFtwYWNrZXQuYWNjb3VudElkXSkge1xuICAgICAgdGhpcy5fbGFzdFNOUGFja2V0W3BhY2tldC5hY2NvdW50SWRdID0ge307XG4gICAgfVxuICAgIGlmKFsna2VlcGFsaXZlJywgJ25vb3AnXS5pbmNsdWRlcyhwYWNrZXQudHlwZSkpIHtcbiAgICAgIHRoaXMuX2xhc3RTTlBhY2tldFtwYWNrZXQuYWNjb3VudElkXVtpbnN0YW5jZUluZGV4XSA9IHBhY2tldDtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgcXVldWUgPSB0aGlzLl93cml0ZVF1ZXVlW3BhY2tldC5hY2NvdW50SWRdLnF1ZXVlO1xuICAgIGlmKCF0aGlzLl9wcmV2aW91c1ByaWNlc1twYWNrZXQuYWNjb3VudElkXSkge1xuICAgICAgdGhpcy5fcHJldmlvdXNQcmljZXNbcGFja2V0LmFjY291bnRJZF0gPSB7fTtcbiAgICB9XG4gICAgXG4gICAgY29uc3QgcHJldlByaWNlID0gdGhpcy5fcHJldmlvdXNQcmljZXNbcGFja2V0LmFjY291bnRJZF1baW5zdGFuY2VJbmRleF07XG4gICAgXG4gICAgaWYocGFja2V0LnR5cGUgIT09ICdwcmljZXMnKSB7XG4gICAgICBpZihwcmV2UHJpY2UpIHtcbiAgICAgICAgdGhpcy5fcmVjb3JkUHJpY2VzKHBhY2tldC5hY2NvdW50SWQsIGluc3RhbmNlSW5kZXgpO1xuICAgICAgfVxuICAgICAgaWYocGFja2V0LnR5cGUgPT09ICdzcGVjaWZpY2F0aW9ucycgJiYgdGhpcy5fY29tcHJlc3NTcGVjaWZpY2F0aW9ucykge1xuICAgICAgICBxdWV1ZS5wdXNoKEpTT04uc3RyaW5naWZ5KHt0eXBlOiBwYWNrZXQudHlwZSwgc2VxdWVuY2VOdW1iZXI6IHBhY2tldC5zZXF1ZW5jZU51bWJlciwgXG4gICAgICAgICAgc2VxdWVuY2VUaW1lc3RhbXA6IHBhY2tldC5zZXF1ZW5jZVRpbWVzdGFtcCwgaW5zdGFuY2VJbmRleH0pKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHF1ZXVlLnB1c2goSlNPTi5zdHJpbmdpZnkocGFja2V0KSk7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIGlmKCF0aGlzLl9jb21wcmVzc1ByaWNlcykge1xuICAgICAgICBxdWV1ZS5wdXNoKEpTT04uc3RyaW5naWZ5KHBhY2tldCkpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgaWYocHJldlByaWNlKSB7XG4gICAgICAgICAgY29uc3QgdmFsaWRTZXF1ZW5jZU51bWJlcnMgPSBbcHJldlByaWNlLmxhc3Quc2VxdWVuY2VOdW1iZXIsIHByZXZQcmljZS5sYXN0LnNlcXVlbmNlTnVtYmVyICsgMV07XG4gICAgICAgICAgaWYodGhpcy5fbGFzdFNOUGFja2V0W3BhY2tldC5hY2NvdW50SWRdW2luc3RhbmNlSW5kZXhdKSB7XG4gICAgICAgICAgICB2YWxpZFNlcXVlbmNlTnVtYmVycy5wdXNoKHRoaXMuX2xhc3RTTlBhY2tldFtwYWNrZXQuYWNjb3VudElkXVtpbnN0YW5jZUluZGV4XS5zZXF1ZW5jZU51bWJlciArIDEpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBpZighdmFsaWRTZXF1ZW5jZU51bWJlcnMuaW5jbHVkZXMocGFja2V0LnNlcXVlbmNlTnVtYmVyKSkge1xuICAgICAgICAgICAgdGhpcy5fcmVjb3JkUHJpY2VzKHBhY2tldC5hY2NvdW50SWQsIGluc3RhbmNlSW5kZXgpO1xuICAgICAgICAgICAgdGhpcy5fZW5zdXJlUHJldmlvdXNQcmljZU9iamVjdChwYWNrZXQuYWNjb3VudElkKTtcbiAgICAgICAgICAgIHRoaXMuX3ByZXZpb3VzUHJpY2VzW3BhY2tldC5hY2NvdW50SWRdW2luc3RhbmNlSW5kZXhdID0ge2ZpcnN0OiBwYWNrZXQsIGxhc3Q6IHBhY2tldH07XG4gICAgICAgICAgICBxdWV1ZS5wdXNoKEpTT04uc3RyaW5naWZ5KHBhY2tldCkpO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB0aGlzLl9wcmV2aW91c1ByaWNlc1twYWNrZXQuYWNjb3VudElkXVtpbnN0YW5jZUluZGV4XS5sYXN0ID0gcGFja2V0O1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB0aGlzLl9lbnN1cmVQcmV2aW91c1ByaWNlT2JqZWN0KHBhY2tldC5hY2NvdW50SWQpO1xuICAgICAgICAgIHRoaXMuX3ByZXZpb3VzUHJpY2VzW3BhY2tldC5hY2NvdW50SWRdW2luc3RhbmNlSW5kZXhdID0ge2ZpcnN0OiBwYWNrZXQsIGxhc3Q6IHBhY2tldH07XG4gICAgICAgICAgcXVldWUucHVzaChKU09OLnN0cmluZ2lmeShwYWNrZXQpKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGxvZyBtZXNzYWdlcyB3aXRoaW4gZGF0ZSBib3VuZHMgYXMgYW4gYXJyYXkgb2Ygb2JqZWN0c1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWQgXG4gICAqIEBwYXJhbSB7RGF0ZX0gZGF0ZUFmdGVyIGRhdGUgdG8gZ2V0IGxvZ3MgYWZ0ZXJcbiAgICogQHBhcmFtIHtEYXRlfSBkYXRlQmVmb3JlIGRhdGUgdG8gZ2V0IGxvZ3MgYmVmb3JlXG4gICAqIEByZXR1cm5zIHtBcnJheTxPYmplY3Q+fSBsb2cgbWVzc2FnZXNcbiAgICovXG4gIGFzeW5jIHJlYWRMb2dzKGFjY291bnRJZCwgZGF0ZUFmdGVyLCBkYXRlQmVmb3JlKSB7XG4gICAgY29uc3QgZm9sZGVycyA9IGF3YWl0IGZzLnJlYWRkaXIodGhpcy5fcm9vdCk7XG4gICAgY29uc3QgcGFja2V0cyA9IFtdO1xuXG4gICAgZm9yIChsZXQgZm9sZGVyIG9mIGZvbGRlcnMpIHtcbiAgICAgIGNvbnN0IGZpbGVQYXRoID0gYCR7dGhpcy5fcm9vdH0vJHtmb2xkZXJ9LyR7YWNjb3VudElkfS5sb2dgO1xuICAgICAgaWYoYXdhaXQgZnMucGF0aEV4aXN0cyhmaWxlUGF0aCkpIHtcbiAgICAgICAgY29uc3QgY29udGVudHMgPSBhd2FpdCBmcy5yZWFkRmlsZShmaWxlUGF0aCwgJ3V0ZjgnKTtcbiAgICAgICAgbGV0IG1lc3NhZ2VzID0gY29udGVudHMuc3BsaXQoJ1xcclxcbicpLmZpbHRlcihtZXNzYWdlID0+IG1lc3NhZ2UubGVuZ3RoKS5tYXAobWVzc2FnZSA9PiB7XG4gICAgICAgICAgcmV0dXJuIHtkYXRlOiBuZXcgRGF0ZShtZXNzYWdlLnNsaWNlKDEsIDI0KSksIG1lc3NhZ2U6IG1lc3NhZ2Uuc2xpY2UoMjYpfTtcbiAgICAgICAgfSk7XG4gICAgICAgIGlmKGRhdGVBZnRlcikge1xuICAgICAgICAgIG1lc3NhZ2VzID0gbWVzc2FnZXMuZmlsdGVyKG1lc3NhZ2UgPT4gbWVzc2FnZS5kYXRlID4gZGF0ZUFmdGVyKTtcbiAgICAgICAgfVxuICAgICAgICBpZihkYXRlQmVmb3JlKSB7XG4gICAgICAgICAgbWVzc2FnZXMgPSBtZXNzYWdlcy5maWx0ZXIobWVzc2FnZSA9PiBtZXNzYWdlLmRhdGUgPCBkYXRlQmVmb3JlKTtcbiAgICAgICAgfVxuICAgICAgICBwYWNrZXRzLnB1c2goLi4ubWVzc2FnZXMpO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBwYWNrZXRzO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgcGF0aCBmb3IgYWNjb3VudCBsb2cgZmlsZVxuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHJldHVybnMge1N0cmluZ30gZmlsZSBwYXRoXG4gICAqL1xuICBhc3luYyBnZXRGaWxlUGF0aChhY2NvdW50SWQpIHtcbiAgICBjb25zdCBmaWxlSW5kZXggPSBNYXRoLmZsb29yKG5ldyBEYXRlKCkuZ2V0SG91cnMoKSAvIHRoaXMuX2xvZ0ZpbGVTaXplSW5Ib3Vycyk7XG4gICAgY29uc3QgZm9sZGVyTmFtZSA9IGAke21vbWVudCgpLmZvcm1hdCgnWVlZWS1NTS1ERCcpfS0ke2ZpbGVJbmRleCA+IDkgPyBmaWxlSW5kZXggOiAnMCcgKyBmaWxlSW5kZXh9YDtcbiAgICBhd2FpdCBmcy5lbnN1cmVEaXIoYCR7dGhpcy5fcm9vdH0vJHtmb2xkZXJOYW1lfWApO1xuICAgIHJldHVybiBgJHt0aGlzLl9yb290fS8ke2ZvbGRlck5hbWV9LyR7YWNjb3VudElkfS5sb2dgO1xuICB9XG5cbiAgLyoqXG4gICAqIEluaXRpYWxpemVzIHRoZSBwYWNrZXQgbG9nZ2VyXG4gICAqL1xuICBzdGFydCgpIHtcbiAgICB0aGlzLl9wcmV2aW91c1ByaWNlcyA9IHt9O1xuICAgIGlmICghdGhpcy5fcmVjb3JkSW50ZXZhbCkge1xuICAgICAgdGhpcy5fcmVjb3JkSW50ZXZhbCA9IHNldEludGVydmFsKCgpID0+IHRoaXMuX2FwcGVuZExvZ3MoKSwgMTAwMCk7XG4gICAgICB0aGlzLl9kZWxldGVPbGRMb2dzSW50ZXJ2YWwgPSBzZXRJbnRlcnZhbCgoKSA9PiB0aGlzLl9kZWxldGVPbGREYXRhKCksIDEwMDAwKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRGVpbml0aWFsaXplcyB0aGUgcGFja2V0IGxvZ2dlclxuICAgKi9cbiAgc3RvcCgpIHtcbiAgICBjbGVhckludGVydmFsKHRoaXMuX3JlY29yZEludGV2YWwpO1xuICAgIGNsZWFySW50ZXJ2YWwodGhpcy5fZGVsZXRlT2xkTG9nc0ludGVydmFsKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZWNvcmRzIHByaWNlIHBhY2tldCBtZXNzYWdlcyB0byBsb2cgZmlsZXNcbiAgICogQHBhcmFtIHtTdHJpbmd9IGFjY291bnRJZCBhY2NvdW50IGlkXG4gICAqL1xuICBfcmVjb3JkUHJpY2VzKGFjY291bnRJZCwgaW5zdGFuY2VOdW1iZXIpIHtcbiAgICBjb25zdCBwcmV2UHJpY2UgPSB0aGlzLl9wcmV2aW91c1ByaWNlc1thY2NvdW50SWRdW2luc3RhbmNlTnVtYmVyXSB8fCB7Zmlyc3Q6IHt9LCBsYXN0Ont9fTtcbiAgICBjb25zdCBxdWV1ZSA9IHRoaXMuX3dyaXRlUXVldWVbYWNjb3VudElkXS5xdWV1ZTtcbiAgICBkZWxldGUgdGhpcy5fcHJldmlvdXNQcmljZXNbYWNjb3VudElkXVtpbnN0YW5jZU51bWJlcl07XG4gICAgaWYoIU9iamVjdC5rZXlzKHRoaXMuX3ByZXZpb3VzUHJpY2VzW2FjY291bnRJZF0pLmxlbmd0aCkge1xuICAgICAgZGVsZXRlIHRoaXMuX3ByZXZpb3VzUHJpY2VzW2FjY291bnRJZF07XG4gICAgfVxuICAgIGlmKHByZXZQcmljZS5maXJzdC5zZXF1ZW5jZU51bWJlciAhPT0gcHJldlByaWNlLmxhc3Quc2VxdWVuY2VOdW1iZXIpIHtcbiAgICAgIHF1ZXVlLnB1c2goSlNPTi5zdHJpbmdpZnkocHJldlByaWNlLmxhc3QpKTtcbiAgICAgIHF1ZXVlLnB1c2goYFJlY29yZGVkIHByaWNlIHBhY2tldHMgJHtwcmV2UHJpY2UuZmlyc3Quc2VxdWVuY2VOdW1iZXJ9YCArXG4gICAgICAgIGAtJHtwcmV2UHJpY2UubGFzdC5zZXF1ZW5jZU51bWJlcn0sIGluc3RhbmNlSW5kZXg6ICR7aW5zdGFuY2VOdW1iZXJ9YCk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFdyaXRlcyBsb2dzIHRvIGZpbGVzXG4gICAqL1xuICBhc3luYyBfYXBwZW5kTG9ncygpIHtcbiAgICBPYmplY3Qua2V5cyh0aGlzLl93cml0ZVF1ZXVlKS5mb3JFYWNoKGFzeW5jIGFjY291bnRJZCA9PiB7XG4gICAgICBjb25zdCBxdWV1ZSA9IHRoaXMuX3dyaXRlUXVldWVbYWNjb3VudElkXTtcbiAgICAgIGlmICghcXVldWUuaXNXcml0aW5nICYmIHF1ZXVlLnF1ZXVlLmxlbmd0aCkge1xuICAgICAgICBxdWV1ZS5pc1dyaXRpbmcgPSB0cnVlO1xuICAgICAgICB0cnkge1xuICAgICAgICAgIGNvbnN0IGZpbGVQYXRoID0gYXdhaXQgdGhpcy5nZXRGaWxlUGF0aChhY2NvdW50SWQpO1xuICAgICAgICAgIGNvbnN0IHdyaXRlU3RyaW5nID0gcXVldWUucXVldWUucmVkdWNlKChhLGIpID0+IGEgKyBcbiAgICAgICAgICBgWyR7bW9tZW50KCkuZm9ybWF0KCdZWVlZLU1NLUREIEhIOm1tOnNzLlNTUycpfV0gJHtifVxcclxcbmAgLCcnKTtcbiAgICAgICAgICBxdWV1ZS5xdWV1ZSA9IFtdO1xuICAgICAgICAgIGF3YWl0IGZzLmFwcGVuZEZpbGUoZmlsZVBhdGgsIHdyaXRlU3RyaW5nKTsgIFxuICAgICAgICB9IGNhdGNoKGVycikge1xuICAgICAgICAgIHRoaXMuX2xvZ2dlci5lcnJvcihgJHthY2NvdW50SWR9OiBGYWlsZWQgdG8gcmVjb3JkIHBhY2tldCBsb2dgLCBlcnIpO1xuICAgICAgICB9XG4gICAgICAgIHF1ZXVlLmlzV3JpdGluZyA9IGZhbHNlO1xuICAgICAgfVxuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIERlbGV0ZXMgZm9sZGVycyB3aGVuIHRoZSBmb2xkZXIgbGltaXQgaXMgZXhjZWVkZWRcbiAgICovXG4gIGFzeW5jIF9kZWxldGVPbGREYXRhKCkge1xuICAgIGNvbnN0IGNvbnRlbnRzID0gYXdhaXQgZnMucmVhZGRpcih0aGlzLl9yb290KTtcbiAgICBjb250ZW50cy5yZXZlcnNlKCkuc2xpY2UodGhpcy5fZmlsZU51bWJlckxpbWl0KS5mb3JFYWNoKGFzeW5jIGZvbGRlck5hbWUgPT4ge1xuICAgICAgYXdhaXQgZnMucmVtb3ZlKGAke3RoaXMuX3Jvb3R9LyR7Zm9sZGVyTmFtZX1gKTsgXG4gICAgfSk7XG4gIH1cblxufVxuIl0sIm5hbWVzIjpbIlBhY2tldExvZ2dlciIsIl9lbnN1cmVQcmV2aW91c1ByaWNlT2JqZWN0IiwiYWNjb3VudElkIiwiX3ByZXZpb3VzUHJpY2VzIiwibG9nUGFja2V0IiwicGFja2V0IiwiaW5zdGFuY2VJbmRleCIsIl93cml0ZVF1ZXVlIiwiaXNXcml0aW5nIiwicXVldWUiLCJ0eXBlIiwiX2xhc3RTTlBhY2tldCIsImluY2x1ZGVzIiwicHJldlByaWNlIiwiX3JlY29yZFByaWNlcyIsIl9jb21wcmVzc1NwZWNpZmljYXRpb25zIiwicHVzaCIsIkpTT04iLCJzdHJpbmdpZnkiLCJzZXF1ZW5jZU51bWJlciIsInNlcXVlbmNlVGltZXN0YW1wIiwiX2NvbXByZXNzUHJpY2VzIiwidmFsaWRTZXF1ZW5jZU51bWJlcnMiLCJsYXN0IiwiZmlyc3QiLCJyZWFkTG9ncyIsImRhdGVBZnRlciIsImRhdGVCZWZvcmUiLCJmb2xkZXJzIiwiZnMiLCJyZWFkZGlyIiwiX3Jvb3QiLCJwYWNrZXRzIiwiZm9sZGVyIiwiZmlsZVBhdGgiLCJwYXRoRXhpc3RzIiwiY29udGVudHMiLCJyZWFkRmlsZSIsIm1lc3NhZ2VzIiwic3BsaXQiLCJmaWx0ZXIiLCJtZXNzYWdlIiwibGVuZ3RoIiwibWFwIiwiZGF0ZSIsIkRhdGUiLCJzbGljZSIsImdldEZpbGVQYXRoIiwiZmlsZUluZGV4IiwiTWF0aCIsImZsb29yIiwiZ2V0SG91cnMiLCJfbG9nRmlsZVNpemVJbkhvdXJzIiwiZm9sZGVyTmFtZSIsIm1vbWVudCIsImZvcm1hdCIsImVuc3VyZURpciIsInN0YXJ0IiwiX3JlY29yZEludGV2YWwiLCJzZXRJbnRlcnZhbCIsIl9hcHBlbmRMb2dzIiwiX2RlbGV0ZU9sZExvZ3NJbnRlcnZhbCIsIl9kZWxldGVPbGREYXRhIiwic3RvcCIsImNsZWFySW50ZXJ2YWwiLCJpbnN0YW5jZU51bWJlciIsIk9iamVjdCIsImtleXMiLCJmb3JFYWNoIiwid3JpdGVTdHJpbmciLCJyZWR1Y2UiLCJhIiwiYiIsImFwcGVuZEZpbGUiLCJlcnIiLCJfbG9nZ2VyIiwiZXJyb3IiLCJyZXZlcnNlIiwiX2ZpbGVOdW1iZXJMaW1pdCIsInJlbW92ZSIsImNvbnN0cnVjdG9yIiwib3B0cyIsInZhbGlkYXRvciIsIk9wdGlvbnNWYWxpZGF0b3IiLCJ2YWxpZGF0ZU5vblplcm8iLCJmaWxlTnVtYmVyTGltaXQiLCJsb2dGaWxlU2l6ZUluSG91cnMiLCJ2YWxpZGF0ZUJvb2xlYW4iLCJjb21wcmVzc1NwZWNpZmljYXRpb25zIiwiY29tcHJlc3NQcmljZXMiLCJMb2dnZXJNYW5hZ2VyIiwiZ2V0TG9nZ2VyIl0sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7OztlQW1CcUJBOzs7Z0VBbEJOOytEQUNJO3lFQUNVOytEQUNIOzs7Ozs7QUFlWCxJQUFBLEFBQU1BLGVBQU4sTUFBTUE7SUF1Qm5CQywyQkFBMkJDLFNBQVMsRUFBRTtRQUNwQyxJQUFHLENBQUMsSUFBSSxDQUFDQyxlQUFlLENBQUNELFVBQVUsRUFBRTtZQUNuQyxJQUFJLENBQUNDLGVBQWUsQ0FBQ0QsVUFBVSxHQUFHLENBQUM7UUFDckM7SUFDRjtJQUVBOzs7R0FHQyxHQUNELHNDQUFzQztJQUN0Q0UsVUFBVUMsTUFBTSxFQUFFO1FBQ2hCLE1BQU1DLGdCQUFnQkQsT0FBT0MsYUFBYSxJQUFJO1FBQzlDLElBQUcsQ0FBQyxJQUFJLENBQUNDLFdBQVcsQ0FBQ0YsT0FBT0gsU0FBUyxDQUFDLEVBQUU7WUFDdEMsSUFBSSxDQUFDSyxXQUFXLENBQUNGLE9BQU9ILFNBQVMsQ0FBQyxHQUFHO2dCQUFDTSxXQUFXO2dCQUFPQyxPQUFPLEVBQUU7WUFBQTtRQUNuRTtRQUNBLElBQUdKLE9BQU9LLElBQUksS0FBSyxVQUFVO1lBQzNCO1FBQ0Y7UUFDQSxJQUFHLENBQUMsSUFBSSxDQUFDQyxhQUFhLENBQUNOLE9BQU9ILFNBQVMsQ0FBQyxFQUFFO1lBQ3hDLElBQUksQ0FBQ1MsYUFBYSxDQUFDTixPQUFPSCxTQUFTLENBQUMsR0FBRyxDQUFDO1FBQzFDO1FBQ0EsSUFBRztZQUFDO1lBQWE7U0FBTyxDQUFDVSxRQUFRLENBQUNQLE9BQU9LLElBQUksR0FBRztZQUM5QyxJQUFJLENBQUNDLGFBQWEsQ0FBQ04sT0FBT0gsU0FBUyxDQUFDLENBQUNJLGNBQWMsR0FBR0Q7WUFDdEQ7UUFDRjtRQUNBLE1BQU1JLFFBQVEsSUFBSSxDQUFDRixXQUFXLENBQUNGLE9BQU9ILFNBQVMsQ0FBQyxDQUFDTyxLQUFLO1FBQ3RELElBQUcsQ0FBQyxJQUFJLENBQUNOLGVBQWUsQ0FBQ0UsT0FBT0gsU0FBUyxDQUFDLEVBQUU7WUFDMUMsSUFBSSxDQUFDQyxlQUFlLENBQUNFLE9BQU9ILFNBQVMsQ0FBQyxHQUFHLENBQUM7UUFDNUM7UUFFQSxNQUFNVyxZQUFZLElBQUksQ0FBQ1YsZUFBZSxDQUFDRSxPQUFPSCxTQUFTLENBQUMsQ0FBQ0ksY0FBYztRQUV2RSxJQUFHRCxPQUFPSyxJQUFJLEtBQUssVUFBVTtZQUMzQixJQUFHRyxXQUFXO2dCQUNaLElBQUksQ0FBQ0MsYUFBYSxDQUFDVCxPQUFPSCxTQUFTLEVBQUVJO1lBQ3ZDO1lBQ0EsSUFBR0QsT0FBT0ssSUFBSSxLQUFLLG9CQUFvQixJQUFJLENBQUNLLHVCQUF1QixFQUFFO2dCQUNuRU4sTUFBTU8sSUFBSSxDQUFDQyxLQUFLQyxTQUFTLENBQUM7b0JBQUNSLE1BQU1MLE9BQU9LLElBQUk7b0JBQUVTLGdCQUFnQmQsT0FBT2MsY0FBYztvQkFDakZDLG1CQUFtQmYsT0FBT2UsaUJBQWlCO29CQUFFZDtnQkFBYTtZQUM5RCxPQUFPO2dCQUNMRyxNQUFNTyxJQUFJLENBQUNDLEtBQUtDLFNBQVMsQ0FBQ2I7WUFDNUI7UUFDRixPQUFPO1lBQ0wsSUFBRyxDQUFDLElBQUksQ0FBQ2dCLGVBQWUsRUFBRTtnQkFDeEJaLE1BQU1PLElBQUksQ0FBQ0MsS0FBS0MsU0FBUyxDQUFDYjtZQUM1QixPQUFPO2dCQUNMLElBQUdRLFdBQVc7b0JBQ1osTUFBTVMsdUJBQXVCO3dCQUFDVCxVQUFVVSxJQUFJLENBQUNKLGNBQWM7d0JBQUVOLFVBQVVVLElBQUksQ0FBQ0osY0FBYyxHQUFHO3FCQUFFO29CQUMvRixJQUFHLElBQUksQ0FBQ1IsYUFBYSxDQUFDTixPQUFPSCxTQUFTLENBQUMsQ0FBQ0ksY0FBYyxFQUFFO3dCQUN0RGdCLHFCQUFxQk4sSUFBSSxDQUFDLElBQUksQ0FBQ0wsYUFBYSxDQUFDTixPQUFPSCxTQUFTLENBQUMsQ0FBQ0ksY0FBYyxDQUFDYSxjQUFjLEdBQUc7b0JBQ2pHO29CQUNBLElBQUcsQ0FBQ0cscUJBQXFCVixRQUFRLENBQUNQLE9BQU9jLGNBQWMsR0FBRzt3QkFDeEQsSUFBSSxDQUFDTCxhQUFhLENBQUNULE9BQU9ILFNBQVMsRUFBRUk7d0JBQ3JDLElBQUksQ0FBQ0wsMEJBQTBCLENBQUNJLE9BQU9ILFNBQVM7d0JBQ2hELElBQUksQ0FBQ0MsZUFBZSxDQUFDRSxPQUFPSCxTQUFTLENBQUMsQ0FBQ0ksY0FBYyxHQUFHOzRCQUFDa0IsT0FBT25COzRCQUFRa0IsTUFBTWxCO3dCQUFNO3dCQUNwRkksTUFBTU8sSUFBSSxDQUFDQyxLQUFLQyxTQUFTLENBQUNiO29CQUM1QixPQUFPO3dCQUNMLElBQUksQ0FBQ0YsZUFBZSxDQUFDRSxPQUFPSCxTQUFTLENBQUMsQ0FBQ0ksY0FBYyxDQUFDaUIsSUFBSSxHQUFHbEI7b0JBQy9EO2dCQUNGLE9BQU87b0JBQ0wsSUFBSSxDQUFDSiwwQkFBMEIsQ0FBQ0ksT0FBT0gsU0FBUztvQkFDaEQsSUFBSSxDQUFDQyxlQUFlLENBQUNFLE9BQU9ILFNBQVMsQ0FBQyxDQUFDSSxjQUFjLEdBQUc7d0JBQUNrQixPQUFPbkI7d0JBQVFrQixNQUFNbEI7b0JBQU07b0JBQ3BGSSxNQUFNTyxJQUFJLENBQUNDLEtBQUtDLFNBQVMsQ0FBQ2I7Z0JBQzVCO1lBQ0Y7UUFDRjtJQUNGO0lBRUE7Ozs7OztHQU1DLEdBQ0QsTUFBTW9CLFNBQVN2QixTQUFTLEVBQUV3QixTQUFTLEVBQUVDLFVBQVUsRUFBRTtRQUMvQyxNQUFNQyxVQUFVLE1BQU1DLGdCQUFFLENBQUNDLE9BQU8sQ0FBQyxJQUFJLENBQUNDLEtBQUs7UUFDM0MsTUFBTUMsVUFBVSxFQUFFO1FBRWxCLEtBQUssSUFBSUMsVUFBVUwsUUFBUztZQUMxQixNQUFNTSxXQUFXLENBQUMsRUFBRSxJQUFJLENBQUNILEtBQUssQ0FBQyxDQUFDLEVBQUVFLE9BQU8sQ0FBQyxFQUFFL0IsVUFBVSxJQUFJLENBQUM7WUFDM0QsSUFBRyxNQUFNMkIsZ0JBQUUsQ0FBQ00sVUFBVSxDQUFDRCxXQUFXO2dCQUNoQyxNQUFNRSxXQUFXLE1BQU1QLGdCQUFFLENBQUNRLFFBQVEsQ0FBQ0gsVUFBVTtnQkFDN0MsSUFBSUksV0FBV0YsU0FBU0csS0FBSyxDQUFDLFFBQVFDLE1BQU0sQ0FBQ0MsQ0FBQUEsVUFBV0EsUUFBUUMsTUFBTSxFQUFFQyxHQUFHLENBQUNGLENBQUFBO29CQUMxRSxPQUFPO3dCQUFDRyxNQUFNLElBQUlDLEtBQUtKLFFBQVFLLEtBQUssQ0FBQyxHQUFHO3dCQUFNTCxTQUFTQSxRQUFRSyxLQUFLLENBQUM7b0JBQUc7Z0JBQzFFO2dCQUNBLElBQUdwQixXQUFXO29CQUNaWSxXQUFXQSxTQUFTRSxNQUFNLENBQUNDLENBQUFBLFVBQVdBLFFBQVFHLElBQUksR0FBR2xCO2dCQUN2RDtnQkFDQSxJQUFHQyxZQUFZO29CQUNiVyxXQUFXQSxTQUFTRSxNQUFNLENBQUNDLENBQUFBLFVBQVdBLFFBQVFHLElBQUksR0FBR2pCO2dCQUN2RDtnQkFDQUssUUFBUWhCLElBQUksSUFBSXNCO1lBQ2xCO1FBQ0Y7UUFFQSxPQUFPTjtJQUNUO0lBRUE7Ozs7R0FJQyxHQUNELE1BQU1lLFlBQVk3QyxTQUFTLEVBQUU7UUFDM0IsTUFBTThDLFlBQVlDLEtBQUtDLEtBQUssQ0FBQyxJQUFJTCxPQUFPTSxRQUFRLEtBQUssSUFBSSxDQUFDQyxtQkFBbUI7UUFDN0UsTUFBTUMsYUFBYSxDQUFDLEVBQUVDLElBQUFBLGVBQU0sSUFBR0MsTUFBTSxDQUFDLGNBQWMsQ0FBQyxFQUFFUCxZQUFZLElBQUlBLFlBQVksTUFBTUEsVUFBVSxDQUFDO1FBQ3BHLE1BQU1uQixnQkFBRSxDQUFDMkIsU0FBUyxDQUFDLENBQUMsRUFBRSxJQUFJLENBQUN6QixLQUFLLENBQUMsQ0FBQyxFQUFFc0IsV0FBVyxDQUFDO1FBQ2hELE9BQU8sQ0FBQyxFQUFFLElBQUksQ0FBQ3RCLEtBQUssQ0FBQyxDQUFDLEVBQUVzQixXQUFXLENBQUMsRUFBRW5ELFVBQVUsSUFBSSxDQUFDO0lBQ3ZEO0lBRUE7O0dBRUMsR0FDRHVELFFBQVE7UUFDTixJQUFJLENBQUN0RCxlQUFlLEdBQUcsQ0FBQztRQUN4QixJQUFJLENBQUMsSUFBSSxDQUFDdUQsY0FBYyxFQUFFO1lBQ3hCLElBQUksQ0FBQ0EsY0FBYyxHQUFHQyxZQUFZLElBQU0sSUFBSSxDQUFDQyxXQUFXLElBQUk7WUFDNUQsSUFBSSxDQUFDQyxzQkFBc0IsR0FBR0YsWUFBWSxJQUFNLElBQUksQ0FBQ0csY0FBYyxJQUFJO1FBQ3pFO0lBQ0Y7SUFFQTs7R0FFQyxHQUNEQyxPQUFPO1FBQ0xDLGNBQWMsSUFBSSxDQUFDTixjQUFjO1FBQ2pDTSxjQUFjLElBQUksQ0FBQ0gsc0JBQXNCO0lBQzNDO0lBRUE7OztHQUdDLEdBQ0QvQyxjQUFjWixTQUFTLEVBQUUrRCxjQUFjLEVBQUU7UUFDdkMsTUFBTXBELFlBQVksSUFBSSxDQUFDVixlQUFlLENBQUNELFVBQVUsQ0FBQytELGVBQWUsSUFBSTtZQUFDekMsT0FBTyxDQUFDO1lBQUdELE1BQUssQ0FBQztRQUFDO1FBQ3hGLE1BQU1kLFFBQVEsSUFBSSxDQUFDRixXQUFXLENBQUNMLFVBQVUsQ0FBQ08sS0FBSztRQUMvQyxPQUFPLElBQUksQ0FBQ04sZUFBZSxDQUFDRCxVQUFVLENBQUMrRCxlQUFlO1FBQ3RELElBQUcsQ0FBQ0MsT0FBT0MsSUFBSSxDQUFDLElBQUksQ0FBQ2hFLGVBQWUsQ0FBQ0QsVUFBVSxFQUFFd0MsTUFBTSxFQUFFO1lBQ3ZELE9BQU8sSUFBSSxDQUFDdkMsZUFBZSxDQUFDRCxVQUFVO1FBQ3hDO1FBQ0EsSUFBR1csVUFBVVcsS0FBSyxDQUFDTCxjQUFjLEtBQUtOLFVBQVVVLElBQUksQ0FBQ0osY0FBYyxFQUFFO1lBQ25FVixNQUFNTyxJQUFJLENBQUNDLEtBQUtDLFNBQVMsQ0FBQ0wsVUFBVVUsSUFBSTtZQUN4Q2QsTUFBTU8sSUFBSSxDQUFDLENBQUMsdUJBQXVCLEVBQUVILFVBQVVXLEtBQUssQ0FBQ0wsY0FBYyxDQUFDLENBQUMsR0FDbkUsQ0FBQyxDQUFDLEVBQUVOLFVBQVVVLElBQUksQ0FBQ0osY0FBYyxDQUFDLGlCQUFpQixFQUFFOEMsZUFBZSxDQUFDO1FBQ3pFO0lBQ0Y7SUFFQTs7R0FFQyxHQUNELE1BQU1MLGNBQWM7UUFDbEJNLE9BQU9DLElBQUksQ0FBQyxJQUFJLENBQUM1RCxXQUFXLEVBQUU2RCxPQUFPLENBQUMsT0FBTWxFO1lBQzFDLE1BQU1PLFFBQVEsSUFBSSxDQUFDRixXQUFXLENBQUNMLFVBQVU7WUFDekMsSUFBSSxDQUFDTyxNQUFNRCxTQUFTLElBQUlDLE1BQU1BLEtBQUssQ0FBQ2lDLE1BQU0sRUFBRTtnQkFDMUNqQyxNQUFNRCxTQUFTLEdBQUc7Z0JBQ2xCLElBQUk7b0JBQ0YsTUFBTTBCLFdBQVcsTUFBTSxJQUFJLENBQUNhLFdBQVcsQ0FBQzdDO29CQUN4QyxNQUFNbUUsY0FBYzVELE1BQU1BLEtBQUssQ0FBQzZELE1BQU0sQ0FBQyxDQUFDQyxHQUFFQyxJQUFNRCxJQUNoRCxDQUFDLENBQUMsRUFBRWpCLElBQUFBLGVBQU0sSUFBR0MsTUFBTSxDQUFDLDJCQUEyQixFQUFFLEVBQUVpQixFQUFFLElBQUksQ0FBQyxFQUFFO29CQUM1RC9ELE1BQU1BLEtBQUssR0FBRyxFQUFFO29CQUNoQixNQUFNb0IsZ0JBQUUsQ0FBQzRDLFVBQVUsQ0FBQ3ZDLFVBQVVtQztnQkFDaEMsRUFBRSxPQUFNSyxLQUFLO29CQUNYLElBQUksQ0FBQ0MsT0FBTyxDQUFDQyxLQUFLLENBQUMsQ0FBQyxFQUFFMUUsVUFBVSw2QkFBNkIsQ0FBQyxFQUFFd0U7Z0JBQ2xFO2dCQUNBakUsTUFBTUQsU0FBUyxHQUFHO1lBQ3BCO1FBQ0Y7SUFDRjtJQUVBOztHQUVDLEdBQ0QsTUFBTXNELGlCQUFpQjtRQUNyQixNQUFNMUIsV0FBVyxNQUFNUCxnQkFBRSxDQUFDQyxPQUFPLENBQUMsSUFBSSxDQUFDQyxLQUFLO1FBQzVDSyxTQUFTeUMsT0FBTyxHQUFHL0IsS0FBSyxDQUFDLElBQUksQ0FBQ2dDLGdCQUFnQixFQUFFVixPQUFPLENBQUMsT0FBTWY7WUFDNUQsTUFBTXhCLGdCQUFFLENBQUNrRCxNQUFNLENBQUMsQ0FBQyxFQUFFLElBQUksQ0FBQ2hELEtBQUssQ0FBQyxDQUFDLEVBQUVzQixXQUFXLENBQUM7UUFDL0M7SUFDRjtJQXhNQTs7O0dBR0MsR0FDRDJCLFlBQVlDLElBQUksQ0FBRTtRQUNoQixNQUFNQyxZQUFZLElBQUlDLHlCQUFnQjtRQUN0Q0YsT0FBT0EsUUFBUSxDQUFDO1FBQ2hCLElBQUksQ0FBQ0gsZ0JBQWdCLEdBQUdJLFVBQVVFLGVBQWUsQ0FBQ0gsS0FBS0ksZUFBZSxFQUFFLElBQUk7UUFDNUUsSUFBSSxDQUFDakMsbUJBQW1CLEdBQUc4QixVQUFVRSxlQUFlLENBQUNILEtBQUtLLGtCQUFrQixFQUFFLEdBQzVFO1FBQ0YsSUFBSSxDQUFDdkUsdUJBQXVCLEdBQUdtRSxVQUFVSyxlQUFlLENBQUNOLEtBQUtPLHNCQUFzQixFQUFFLE1BQ3BGO1FBQ0YsSUFBSSxDQUFDbkUsZUFBZSxHQUFHNkQsVUFBVUssZUFBZSxDQUFDTixLQUFLUSxjQUFjLEVBQUUsTUFBTTtRQUM1RSxJQUFJLENBQUN0RixlQUFlLEdBQUcsQ0FBQztRQUN4QixJQUFJLENBQUNRLGFBQWEsR0FBRyxDQUFDO1FBQ3RCLElBQUksQ0FBQ0osV0FBVyxHQUFHLENBQUM7UUFDcEIsSUFBSSxDQUFDd0IsS0FBSyxHQUFHO1FBQ2IsSUFBSSxDQUFDNEMsT0FBTyxHQUFHZSxlQUFhLENBQUNDLFNBQVMsQ0FBQztRQUN2QzlELGdCQUFFLENBQUMyQixTQUFTLENBQUMsSUFBSSxDQUFDekIsS0FBSztJQUN6QjtBQXVMRiJ9