bcoin: start switching to class syntax.
This commit is contained in:
parent
f9eba3f5a6
commit
a79c2b0b1a
File diff suppressed because it is too large
Load Diff
@ -690,7 +690,7 @@ Mempool.prototype.has = function has(hash) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Mempool.prototype.exists = function exists(hash) {
|
Mempool.prototype.exists = function exists(hash) {
|
||||||
if (this.locker.hasPending(hash))
|
if (this.locker.pending(hash))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (this.hasOrphan(hash))
|
if (this.hasOrphan(hash))
|
||||||
|
|||||||
@ -93,6 +93,14 @@ exports.MAX_BLOCK_SIGOPS = 1000000 / 50;
|
|||||||
|
|
||||||
exports.MAX_BLOCK_SIGOPS_COST = 80000;
|
exports.MAX_BLOCK_SIGOPS_COST = 80000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of set to pick median time from.
|
||||||
|
* @const {Number}
|
||||||
|
* @default
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.MEDIAN_TIMESPAN = 11;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* What bits to set in version
|
* What bits to set in version
|
||||||
* for versionbits blocks.
|
* for versionbits blocks.
|
||||||
|
|||||||
@ -9,388 +9,428 @@
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a promise-resolving event emitter.
|
* Async Emitter
|
||||||
* @alias module:utils.AsyncEmitter
|
* @alias module:utils.AsyncEmitter
|
||||||
* @see EventEmitter
|
* @see EventEmitter
|
||||||
* @constructor
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function AsyncEmitter() {
|
class AsyncEmitter {
|
||||||
if (!(this instanceof AsyncEmitter))
|
/**
|
||||||
return new AsyncEmitter();
|
* Create an async emitter.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
|
||||||
this._events = Object.create(null);
|
constructor() {
|
||||||
|
this._events = Object.create(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener.
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} handler
|
||||||
|
*/
|
||||||
|
|
||||||
|
addListener(type, handler) {
|
||||||
|
return this._push(type, handler, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener.
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} handler
|
||||||
|
*/
|
||||||
|
|
||||||
|
on(type, handler) {
|
||||||
|
return this.addListener(type, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener to execute once.
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} handler
|
||||||
|
*/
|
||||||
|
|
||||||
|
once(type, handler) {
|
||||||
|
return this._push(type, handler, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepend a listener.
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} handler
|
||||||
|
*/
|
||||||
|
|
||||||
|
prependListener(type, handler) {
|
||||||
|
return this._unshift(type, handler, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepend a listener to execute once.
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} handler
|
||||||
|
*/
|
||||||
|
|
||||||
|
prependOnceListener(type, handler) {
|
||||||
|
return this._unshift(type, handler, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a listener.
|
||||||
|
* @private
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} handler
|
||||||
|
* @param {Boolean} once
|
||||||
|
*/
|
||||||
|
|
||||||
|
_push(type, handler, once) {
|
||||||
|
assert(typeof type === 'string', '`type` must be a string.');
|
||||||
|
|
||||||
|
if (!this._events[type])
|
||||||
|
this._events[type] = [];
|
||||||
|
|
||||||
|
this.emit('newListener', type, handler);
|
||||||
|
|
||||||
|
this._events[type].push(new Listener(handler, once));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unshift a listener.
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} handler
|
||||||
|
* @param {Boolean} once
|
||||||
|
*/
|
||||||
|
|
||||||
|
_unshift(type, handler, once) {
|
||||||
|
assert(typeof type === 'string', '`type` must be a string.');
|
||||||
|
|
||||||
|
if (!this._events[type])
|
||||||
|
this._events[type] = [];
|
||||||
|
|
||||||
|
this.emit('newListener', type, handler);
|
||||||
|
|
||||||
|
this._events[type].unshift(new Listener(handler, once));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a listener.
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} handler
|
||||||
|
*/
|
||||||
|
|
||||||
|
removeListener(type, handler) {
|
||||||
|
assert(typeof type === 'string', '`type` must be a string.');
|
||||||
|
|
||||||
|
const listeners = this._events[type];
|
||||||
|
|
||||||
|
if (!listeners)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let index = -1;
|
||||||
|
|
||||||
|
for (let i = 0; i < listeners.length; i++) {
|
||||||
|
const listener = listeners[i];
|
||||||
|
if (listener.handler === handler) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index === -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
splice(listeners, index);
|
||||||
|
|
||||||
|
if (listeners.length === 0)
|
||||||
|
delete this._events[type];
|
||||||
|
|
||||||
|
this.emit('removeListener', type, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set max listeners.
|
||||||
|
* @param {Number} max
|
||||||
|
*/
|
||||||
|
|
||||||
|
setMaxListeners(max) {
|
||||||
|
assert(typeof max === 'number', '`max` must be a number.');
|
||||||
|
assert(max >= 0, '`max` must be non-negative.');
|
||||||
|
assert(Number.isSafeInteger(max), '`max` must be an integer.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all listeners.
|
||||||
|
* @param {String?} type
|
||||||
|
*/
|
||||||
|
|
||||||
|
removeAllListeners(type) {
|
||||||
|
if (arguments.length === 0) {
|
||||||
|
this._events = Object.create(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(typeof type === 'string', '`type` must be a string.');
|
||||||
|
|
||||||
|
delete this._events[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get listeners array.
|
||||||
|
* @param {String} type
|
||||||
|
* @returns {Function[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
listeners(type) {
|
||||||
|
assert(typeof type === 'string', '`type` must be a string.');
|
||||||
|
|
||||||
|
const listeners = this._events[type];
|
||||||
|
|
||||||
|
if (!listeners)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (const {handler} of listeners)
|
||||||
|
result.push(handler);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get listener count for an event.
|
||||||
|
* @param {String} type
|
||||||
|
*/
|
||||||
|
|
||||||
|
listenerCount(type) {
|
||||||
|
assert(typeof type === 'string', '`type` must be a string.');
|
||||||
|
|
||||||
|
const listeners = this._events[type];
|
||||||
|
|
||||||
|
if (!listeners)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return listeners.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get event names.
|
||||||
|
* @returns {String[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
eventNames() {
|
||||||
|
return Object.keys(this._events);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit an event synchronously.
|
||||||
|
* @param {String} type
|
||||||
|
* @param {...Object} args
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
emit(type) {
|
||||||
|
try {
|
||||||
|
this._emit.apply(this, arguments);
|
||||||
|
} catch (e) {
|
||||||
|
if (type === 'error')
|
||||||
|
throw e;
|
||||||
|
|
||||||
|
this._emit('error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit an event synchronously.
|
||||||
|
* @private
|
||||||
|
* @param {String} type
|
||||||
|
* @param {...Object} args
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
_emit(type) {
|
||||||
|
assert(typeof type === 'string', '`type` must be a string.');
|
||||||
|
|
||||||
|
const listeners = this._events[type];
|
||||||
|
|
||||||
|
if (!listeners) {
|
||||||
|
if (type === 'error') {
|
||||||
|
const msg = arguments[1];
|
||||||
|
|
||||||
|
if (msg instanceof Error)
|
||||||
|
throw msg;
|
||||||
|
|
||||||
|
const err = new Error(`Uncaught, unspecified "error" event. (${msg})`);
|
||||||
|
err.context = msg;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(listeners.length > 0);
|
||||||
|
|
||||||
|
let args = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < listeners.length; i++) {
|
||||||
|
const listener = listeners[i];
|
||||||
|
const handler = listener.handler;
|
||||||
|
|
||||||
|
if (listener.once) {
|
||||||
|
splice(listeners, i);
|
||||||
|
if (listeners.length === 0)
|
||||||
|
delete this._events[type];
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (arguments.length) {
|
||||||
|
case 1:
|
||||||
|
handler();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
handler(arguments[1]);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
handler(arguments[1], arguments[2]);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
handler(arguments[1], arguments[2], arguments[3]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!args) {
|
||||||
|
args = new Array(arguments.length - 1);
|
||||||
|
for (let j = 1; j < arguments.length; j++)
|
||||||
|
args[j - 1] = arguments[j];
|
||||||
|
}
|
||||||
|
handler.apply(null, args);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit an event. Wait for promises to resolve.
|
||||||
|
* @method
|
||||||
|
* @param {String} type
|
||||||
|
* @param {...Object} args
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async emitAsync(type) {
|
||||||
|
try {
|
||||||
|
await this._emitAsync.apply(this, arguments);
|
||||||
|
} catch (e) {
|
||||||
|
if (type === 'error')
|
||||||
|
throw e;
|
||||||
|
|
||||||
|
await this._emitAsync('error', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit an event. Wait for promises to resolve.
|
||||||
|
* @private
|
||||||
|
* @param {String} type
|
||||||
|
* @param {...Object} args
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async _emitAsync(type) {
|
||||||
|
assert(typeof type === 'string', '`type` must be a string.');
|
||||||
|
|
||||||
|
const listeners = this._events[type];
|
||||||
|
|
||||||
|
if (!listeners) {
|
||||||
|
if (type === 'error') {
|
||||||
|
const msg = arguments[1];
|
||||||
|
|
||||||
|
if (msg instanceof Error)
|
||||||
|
throw msg;
|
||||||
|
|
||||||
|
const err = new Error(`Uncaught, unspecified "error" event. (${msg})`);
|
||||||
|
err.context = msg;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(listeners.length > 0);
|
||||||
|
|
||||||
|
let args = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < listeners.length; i++) {
|
||||||
|
const listener = listeners[i];
|
||||||
|
const handler = listener.handler;
|
||||||
|
|
||||||
|
if (listener.once) {
|
||||||
|
splice(listeners, i);
|
||||||
|
if (listeners.length === 0)
|
||||||
|
delete this._events[type];
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (arguments.length) {
|
||||||
|
case 1:
|
||||||
|
await handler();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
await handler(arguments[1]);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
await handler(arguments[1], arguments[2]);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
await handler(arguments[1], arguments[2], arguments[3]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!args) {
|
||||||
|
args = new Array(arguments.length - 1);
|
||||||
|
for (let j = 1; j < arguments.length; j++)
|
||||||
|
args[j - 1] = arguments[j];
|
||||||
|
}
|
||||||
|
await handler.apply(null, args);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a listener.
|
|
||||||
* @param {String} type
|
|
||||||
* @param {Function} handler
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype.addListener = function addListener(type, handler) {
|
|
||||||
return this._push(type, handler, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a listener.
|
|
||||||
* @param {String} type
|
|
||||||
* @param {Function} handler
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype.on = function on(type, handler) {
|
|
||||||
return this.addListener(type, handler);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a listener to execute once.
|
|
||||||
* @param {String} type
|
|
||||||
* @param {Function} handler
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype.once = function once(type, handler) {
|
|
||||||
return this._push(type, handler, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepend a listener.
|
|
||||||
* @param {String} type
|
|
||||||
* @param {Function} handler
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype.prependListener = function prependListener(type, handler) {
|
|
||||||
return this._unshift(type, handler, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepend a listener to execute once.
|
|
||||||
* @param {String} type
|
|
||||||
* @param {Function} handler
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype.prependOnceListener = function prependOnceListener(type, handler) {
|
|
||||||
return this._unshift(type, handler, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Push a listener.
|
|
||||||
* @private
|
|
||||||
* @param {String} type
|
|
||||||
* @param {Function} handler
|
|
||||||
* @param {Boolean} once
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype._push = function _push(type, handler, once) {
|
|
||||||
assert(typeof type === 'string', '`type` must be a string.');
|
|
||||||
|
|
||||||
if (!this._events[type])
|
|
||||||
this._events[type] = [];
|
|
||||||
|
|
||||||
this._events[type].push(new Listener(handler, once));
|
|
||||||
|
|
||||||
this.emit('newListener', type, handler);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unshift a listener.
|
|
||||||
* @param {String} type
|
|
||||||
* @param {Function} handler
|
|
||||||
* @param {Boolean} once
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype._unshift = function _unshift(type, handler, once) {
|
|
||||||
assert(typeof type === 'string', '`type` must be a string.');
|
|
||||||
|
|
||||||
if (!this._events[type])
|
|
||||||
this._events[type] = [];
|
|
||||||
|
|
||||||
this._events[type].unshift(new Listener(handler, once));
|
|
||||||
|
|
||||||
this.emit('newListener', type, handler);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a listener.
|
|
||||||
* @param {String} type
|
|
||||||
* @param {Function} handler
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype.removeListener = function removeListener(type, handler) {
|
|
||||||
assert(typeof type === 'string', '`type` must be a string.');
|
|
||||||
|
|
||||||
const listeners = this._events[type];
|
|
||||||
|
|
||||||
if (!listeners)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let index = -1;
|
|
||||||
|
|
||||||
for (let i = 0; i < listeners.length; i++) {
|
|
||||||
const listener = listeners[i];
|
|
||||||
if (listener.handler === handler) {
|
|
||||||
index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index === -1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
listeners.splice(index, 1);
|
|
||||||
|
|
||||||
if (listeners.length === 0)
|
|
||||||
delete this._events[type];
|
|
||||||
|
|
||||||
this.emit('removeListener', type, handler);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set max listeners.
|
|
||||||
* @param {Number} max
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype.setMaxListeners = function setMaxListeners(max) {
|
|
||||||
assert(typeof max === 'number', '`max` must be a number.');
|
|
||||||
assert(max >= 0, '`max` must be non-negative.');
|
|
||||||
assert(Number.isSafeInteger(max), '`max` must be an integer.');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all listeners.
|
|
||||||
* @param {String?} type
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
|
|
||||||
if (arguments.length === 0) {
|
|
||||||
this._events = Object.create(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(typeof type === 'string', '`type` must be a string.');
|
|
||||||
|
|
||||||
delete this._events[type];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get listeners array.
|
|
||||||
* @param {String} type
|
|
||||||
* @returns {Function[]}
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype.listeners = function listeners(type) {
|
|
||||||
assert(typeof type === 'string', '`type` must be a string.');
|
|
||||||
|
|
||||||
const listeners = this._events[type];
|
|
||||||
|
|
||||||
if (!listeners)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
const result = [];
|
|
||||||
|
|
||||||
for (const {handler} of listeners)
|
|
||||||
result.push(handler);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get listener count for an event.
|
|
||||||
* @param {String} type
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype.listenerCount = function listenerCount(type) {
|
|
||||||
assert(typeof type === 'string', '`type` must be a string.');
|
|
||||||
|
|
||||||
const listeners = this._events[type];
|
|
||||||
|
|
||||||
if (!listeners)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return listeners.length;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emit an event synchronously.
|
|
||||||
* @param {String} type
|
|
||||||
* @param {...Object} args
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype.emit = function emit(type) {
|
|
||||||
try {
|
|
||||||
this._emit.apply(this, arguments);
|
|
||||||
} catch (e) {
|
|
||||||
if (type === 'error')
|
|
||||||
throw e;
|
|
||||||
|
|
||||||
this._emit('error', e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emit an event synchronously.
|
|
||||||
* @private
|
|
||||||
* @param {String} type
|
|
||||||
* @param {...Object} args
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype._emit = function _emit(type) {
|
|
||||||
assert(typeof type === 'string', '`type` must be a string.');
|
|
||||||
|
|
||||||
const listeners = this._events[type];
|
|
||||||
|
|
||||||
if (!listeners) {
|
|
||||||
if (type === 'error') {
|
|
||||||
const error = arguments[1];
|
|
||||||
|
|
||||||
if (error instanceof Error)
|
|
||||||
throw error;
|
|
||||||
|
|
||||||
const err = new Error(`Uncaught, unspecified "error" event. (${error})`);
|
|
||||||
err.context = error;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(listeners.length > 0);
|
|
||||||
|
|
||||||
let args = null;
|
|
||||||
|
|
||||||
for (let i = 0; i < listeners.length; i++) {
|
|
||||||
const listener = listeners[i];
|
|
||||||
const handler = listener.handler;
|
|
||||||
|
|
||||||
if (listener.once) {
|
|
||||||
listeners.splice(i, 1);
|
|
||||||
i -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (arguments.length) {
|
|
||||||
case 1:
|
|
||||||
handler();
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
handler(arguments[1]);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
handler(arguments[1], arguments[2]);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
handler(arguments[1], arguments[2], arguments[3]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (!args) {
|
|
||||||
args = new Array(arguments.length - 1);
|
|
||||||
for (let j = 1; j < arguments.length; j++)
|
|
||||||
args[j - 1] = arguments[j];
|
|
||||||
}
|
|
||||||
handler.apply(null, args);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emit an event. Wait for promises to resolve.
|
|
||||||
* @method
|
|
||||||
* @param {String} type
|
|
||||||
* @param {...Object} args
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype.emitAsync = async function emitAsync(type) {
|
|
||||||
try {
|
|
||||||
await this._emitAsync.apply(this, arguments);
|
|
||||||
} catch (e) {
|
|
||||||
if (type === 'error')
|
|
||||||
throw e;
|
|
||||||
|
|
||||||
await this._emitAsync('error', e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emit an event. Wait for promises to resolve.
|
|
||||||
* @private
|
|
||||||
* @param {String} type
|
|
||||||
* @param {...Object} args
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
AsyncEmitter.prototype._emitAsync = async function _emitAsync(type) {
|
|
||||||
assert(typeof type === 'string', '`type` must be a string.');
|
|
||||||
|
|
||||||
const listeners = this._events[type];
|
|
||||||
|
|
||||||
if (!listeners) {
|
|
||||||
if (type === 'error') {
|
|
||||||
const error = arguments[1];
|
|
||||||
|
|
||||||
if (error instanceof Error)
|
|
||||||
throw error;
|
|
||||||
|
|
||||||
const err = new Error(`Uncaught, unspecified "error" event. (${error})`);
|
|
||||||
err.context = error;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(listeners.length > 0);
|
|
||||||
|
|
||||||
let args = null;
|
|
||||||
|
|
||||||
for (let i = 0; i < listeners.length; i++) {
|
|
||||||
const listener = listeners[i];
|
|
||||||
const handler = listener.handler;
|
|
||||||
|
|
||||||
if (listener.once) {
|
|
||||||
listeners.splice(i, 1);
|
|
||||||
i -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (arguments.length) {
|
|
||||||
case 1:
|
|
||||||
await handler();
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
await handler(arguments[1]);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
await handler(arguments[1], arguments[2]);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
await handler(arguments[1], arguments[2], arguments[3]);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (!args) {
|
|
||||||
args = new Array(arguments.length - 1);
|
|
||||||
for (let j = 1; j < arguments.length; j++)
|
|
||||||
args[j - 1] = arguments[j];
|
|
||||||
}
|
|
||||||
await handler.apply(null, args);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event Listener
|
* Event Listener
|
||||||
* @constructor
|
|
||||||
* @ignore
|
* @ignore
|
||||||
* @param {Function} handler
|
|
||||||
* @param {Boolean} once
|
|
||||||
* @property {Function} handler
|
* @property {Function} handler
|
||||||
* @property {Boolean} once
|
* @property {Boolean} once
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function Listener(handler, once) {
|
class Listener {
|
||||||
assert(typeof handler === 'function', '`handler` must be a function.');
|
/**
|
||||||
assert(typeof once === 'boolean', '`once` must be a function.');
|
* Create an event listener.
|
||||||
this.handler = handler;
|
* @constructor
|
||||||
this.once = once;
|
* @param {Function} handler
|
||||||
|
* @param {Boolean} once
|
||||||
|
*/
|
||||||
|
|
||||||
|
constructor(handler, once) {
|
||||||
|
assert(typeof handler === 'function', '`handler` must be a function.');
|
||||||
|
assert(typeof once === 'boolean', '`once` must be a function.');
|
||||||
|
this.handler = handler;
|
||||||
|
this.once = once;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
function splice(list, i) {
|
||||||
|
if (i === 0) {
|
||||||
|
list.shift();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let k = i + 1;
|
||||||
|
|
||||||
|
while (k < list.length)
|
||||||
|
list[i++] = list[k++];
|
||||||
|
|
||||||
|
list.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@ -79,7 +79,25 @@ exports.remove = function remove(items, item, compare) {
|
|||||||
if (i === -1)
|
if (i === -1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
items.splice(i, 1);
|
splice(items, i);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
function splice(list, i) {
|
||||||
|
if (i === 0) {
|
||||||
|
list.shift();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let k = i + 1;
|
||||||
|
|
||||||
|
while (k < list.length)
|
||||||
|
list[i++] = list[k++];
|
||||||
|
|
||||||
|
list.pop();
|
||||||
|
}
|
||||||
|
|||||||
@ -11,221 +11,224 @@ const assert = require('assert');
|
|||||||
/**
|
/**
|
||||||
* Binary Heap
|
* Binary Heap
|
||||||
* @alias module:utils.Heap
|
* @alias module:utils.Heap
|
||||||
* @constructor
|
|
||||||
* @param {Function?} compare
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function Heap(compare) {
|
class Heap {
|
||||||
if (!(this instanceof Heap))
|
/**
|
||||||
return new Heap(compare);
|
* Create a binary heap.
|
||||||
|
* @constructor
|
||||||
|
* @param {Function?} compare
|
||||||
|
*/
|
||||||
|
|
||||||
this.compare = comparator;
|
constructor(compare) {
|
||||||
this.items = [];
|
this.compare = comparator;
|
||||||
|
this.items = [];
|
||||||
|
|
||||||
if (compare)
|
if (compare)
|
||||||
this.set(compare);
|
this.set(compare);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize and sort heap.
|
||||||
|
*/
|
||||||
|
|
||||||
|
init() {
|
||||||
|
const n = this.items.length;
|
||||||
|
|
||||||
|
if (n <= 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (let i = (n / 2 | 0) - 1; i >= 0; i--)
|
||||||
|
this.down(i, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get heap size.
|
||||||
|
* @returns {Number}
|
||||||
|
*/
|
||||||
|
|
||||||
|
size() {
|
||||||
|
return this.items.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set comparator.
|
||||||
|
* @param {Function} compare
|
||||||
|
*/
|
||||||
|
|
||||||
|
set(compare) {
|
||||||
|
assert(typeof compare === 'function',
|
||||||
|
'Comparator must be a function.');
|
||||||
|
this.compare = compare;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push item onto heap.
|
||||||
|
* @param {Object} item
|
||||||
|
* @returns {Number}
|
||||||
|
*/
|
||||||
|
|
||||||
|
insert(item) {
|
||||||
|
this.items.push(item);
|
||||||
|
this.up(this.items.length - 1);
|
||||||
|
return this.items.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pop next item off of heap.
|
||||||
|
* @param {Object} item
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
shift() {
|
||||||
|
if (this.items.length === 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const n = this.items.length - 1;
|
||||||
|
|
||||||
|
this.swap(0, n);
|
||||||
|
this.down(0, n);
|
||||||
|
|
||||||
|
return this.items.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove item from heap.
|
||||||
|
* @param {Number} i
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
remove(i) {
|
||||||
|
if (this.items.length === 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const n = this.items.length - 1;
|
||||||
|
|
||||||
|
if (i < 0 || i > n)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (n !== i) {
|
||||||
|
this.swap(i, n);
|
||||||
|
this.down(i, n);
|
||||||
|
this.up(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.items.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swap indicies.
|
||||||
|
* @private
|
||||||
|
* @param {Number} a
|
||||||
|
* @param {Number} b
|
||||||
|
*/
|
||||||
|
|
||||||
|
swap(a, b) {
|
||||||
|
const x = this.items[a];
|
||||||
|
const y = this.items[b];
|
||||||
|
this.items[a] = y;
|
||||||
|
this.items[b] = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare indicies.
|
||||||
|
* @private
|
||||||
|
* @param {Number} i
|
||||||
|
* @param {Number} j
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
less(i, j) {
|
||||||
|
return this.compare(this.items[i], this.items[j]) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bubble item down.
|
||||||
|
* @private
|
||||||
|
* @param {Number} i
|
||||||
|
* @param {Number} n
|
||||||
|
*/
|
||||||
|
|
||||||
|
down(i, n) {
|
||||||
|
for (;;) {
|
||||||
|
const l = 2 * i + 1;
|
||||||
|
|
||||||
|
assert(l >= 0);
|
||||||
|
|
||||||
|
if (l < 0 || l >= n)
|
||||||
|
break;
|
||||||
|
|
||||||
|
let j = l;
|
||||||
|
const r = l + 1;
|
||||||
|
|
||||||
|
if (r < n && !this.less(l, r))
|
||||||
|
j = r;
|
||||||
|
|
||||||
|
if (!this.less(j, i))
|
||||||
|
break;
|
||||||
|
|
||||||
|
this.swap(i, j);
|
||||||
|
i = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bubble item up.
|
||||||
|
* @private
|
||||||
|
* @param {Number} i
|
||||||
|
*/
|
||||||
|
|
||||||
|
up(i) {
|
||||||
|
for (;;) {
|
||||||
|
const j = (i - 1) / 2 | 0;
|
||||||
|
|
||||||
|
assert(j >= 0);
|
||||||
|
|
||||||
|
if (j < 0 || j === i)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!this.less(i, j))
|
||||||
|
break;
|
||||||
|
|
||||||
|
this.swap(j, i);
|
||||||
|
i = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert heap to sorted array.
|
||||||
|
* @returns {Object[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
toArray() {
|
||||||
|
const heap = new Heap();
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
heap.compare = this.compare;
|
||||||
|
heap.items = this.items.slice();
|
||||||
|
|
||||||
|
while (heap.size() > 0)
|
||||||
|
result.push(heap.shift());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate heap from array and comparator.
|
||||||
|
* @param {Function} compare
|
||||||
|
* @param {Object[]} items
|
||||||
|
* @returns {Heap}
|
||||||
|
*/
|
||||||
|
|
||||||
|
static fromArray(compare, items) {
|
||||||
|
const heap = new Heap();
|
||||||
|
heap.set(compare);
|
||||||
|
heap.items = items;
|
||||||
|
heap.init();
|
||||||
|
return heap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize and sort heap.
|
|
||||||
*/
|
|
||||||
|
|
||||||
Heap.prototype.init = function init() {
|
|
||||||
const n = this.items.length;
|
|
||||||
|
|
||||||
if (n <= 1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (let i = (n / 2 | 0) - 1; i >= 0; i--)
|
|
||||||
this.down(i, n);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get heap size.
|
|
||||||
* @returns {Number}
|
|
||||||
*/
|
|
||||||
|
|
||||||
Heap.prototype.size = function size() {
|
|
||||||
return this.items.length;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set comparator.
|
|
||||||
* @param {Function} compare
|
|
||||||
*/
|
|
||||||
|
|
||||||
Heap.prototype.set = function set(compare) {
|
|
||||||
assert(typeof compare === 'function',
|
|
||||||
'Comparator must be a function.');
|
|
||||||
this.compare = compare;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Push item onto heap.
|
|
||||||
* @param {Object} item
|
|
||||||
* @returns {Number}
|
|
||||||
*/
|
|
||||||
|
|
||||||
Heap.prototype.insert = function insert(item) {
|
|
||||||
this.items.push(item);
|
|
||||||
this.up(this.items.length - 1);
|
|
||||||
return this.items.length;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pop next item off of heap.
|
|
||||||
* @param {Object} item
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
|
|
||||||
Heap.prototype.shift = function shift() {
|
|
||||||
if (this.items.length === 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
const n = this.items.length - 1;
|
|
||||||
|
|
||||||
this.swap(0, n);
|
|
||||||
this.down(0, n);
|
|
||||||
|
|
||||||
return this.items.pop();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove item from heap.
|
|
||||||
* @param {Number} i
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
|
|
||||||
Heap.prototype.remove = function remove(i) {
|
|
||||||
if (this.items.length === 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
const n = this.items.length - 1;
|
|
||||||
|
|
||||||
if (i < 0 || i > n)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (n !== i) {
|
|
||||||
this.swap(i, n);
|
|
||||||
this.down(i, n);
|
|
||||||
this.up(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.items.pop();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Swap indicies.
|
|
||||||
* @private
|
|
||||||
* @param {Number} a
|
|
||||||
* @param {Number} b
|
|
||||||
*/
|
|
||||||
|
|
||||||
Heap.prototype.swap = function swap(a, b) {
|
|
||||||
const x = this.items[a];
|
|
||||||
const y = this.items[b];
|
|
||||||
this.items[a] = y;
|
|
||||||
this.items[b] = x;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare indicies.
|
|
||||||
* @private
|
|
||||||
* @param {Number} i
|
|
||||||
* @param {Number} j
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
Heap.prototype.less = function less(i, j) {
|
|
||||||
return this.compare(this.items[i], this.items[j]) < 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bubble item down.
|
|
||||||
* @private
|
|
||||||
* @param {Number} i
|
|
||||||
* @param {Number} n
|
|
||||||
*/
|
|
||||||
|
|
||||||
Heap.prototype.down = function down(i, n) {
|
|
||||||
for (;;) {
|
|
||||||
const l = 2 * i + 1;
|
|
||||||
|
|
||||||
assert(l >= 0);
|
|
||||||
|
|
||||||
if (l < 0 || l >= n)
|
|
||||||
break;
|
|
||||||
|
|
||||||
let j = l;
|
|
||||||
const r = l + 1;
|
|
||||||
|
|
||||||
if (r < n && !this.less(l, r))
|
|
||||||
j = r;
|
|
||||||
|
|
||||||
if (!this.less(j, i))
|
|
||||||
break;
|
|
||||||
|
|
||||||
this.swap(i, j);
|
|
||||||
i = j;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bubble item up.
|
|
||||||
* @private
|
|
||||||
* @param {Number} i
|
|
||||||
*/
|
|
||||||
|
|
||||||
Heap.prototype.up = function up(i) {
|
|
||||||
for (;;) {
|
|
||||||
const j = (i - 1) / 2 | 0;
|
|
||||||
|
|
||||||
assert(j >= 0);
|
|
||||||
|
|
||||||
if (j < 0 || j === i)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (!this.less(i, j))
|
|
||||||
break;
|
|
||||||
|
|
||||||
this.swap(j, i);
|
|
||||||
i = j;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert heap to sorted array.
|
|
||||||
* @returns {Object[]}
|
|
||||||
*/
|
|
||||||
|
|
||||||
Heap.prototype.toArray = function toArray() {
|
|
||||||
const heap = new Heap();
|
|
||||||
const result = [];
|
|
||||||
|
|
||||||
heap.compare = this.compare;
|
|
||||||
heap.items = this.items.slice();
|
|
||||||
|
|
||||||
while (heap.size() > 0)
|
|
||||||
result.push(heap.shift());
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiate heap from array and comparator.
|
|
||||||
* @param {Function} compare
|
|
||||||
* @param {Object[]} items
|
|
||||||
* @returns {Heap}
|
|
||||||
*/
|
|
||||||
|
|
||||||
Heap.fromArray = function fromArray(compare, items) {
|
|
||||||
const heap = new Heap();
|
|
||||||
heap.set(compare);
|
|
||||||
heap.items = items;
|
|
||||||
heap.init();
|
|
||||||
return heap;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Helpers
|
* Helpers
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -9,263 +9,273 @@
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A double linked list.
|
* Double Linked List
|
||||||
* @alias module:utils.List
|
* @alias module:utils.List
|
||||||
* @constructor
|
|
||||||
* @property {ListItem|null} head
|
|
||||||
* @property {ListItem|null} tail
|
|
||||||
* @property {Number} size
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function List() {
|
class List {
|
||||||
if (!(this instanceof List))
|
/**
|
||||||
return new List();
|
* Create a list.
|
||||||
|
* @constructor
|
||||||
|
* @property {ListItem|null} head
|
||||||
|
* @property {ListItem|null} tail
|
||||||
|
* @property {Number} size
|
||||||
|
*/
|
||||||
|
|
||||||
this.head = null;
|
constructor() {
|
||||||
this.tail = null;
|
this.head = null;
|
||||||
this.size = 0;
|
this.tail = null;
|
||||||
}
|
this.size = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the cache. Clear all items.
|
|
||||||
*/
|
|
||||||
|
|
||||||
List.prototype.reset = function reset() {
|
|
||||||
let item, next;
|
|
||||||
|
|
||||||
for (item = this.head; item; item = next) {
|
|
||||||
next = item.next;
|
|
||||||
item.prev = null;
|
|
||||||
item.next = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(!item);
|
/**
|
||||||
|
* Reset the cache. Clear all items.
|
||||||
|
*/
|
||||||
|
|
||||||
this.head = null;
|
reset() {
|
||||||
this.tail = null;
|
let item, next;
|
||||||
this.size = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
for (item = this.head; item; item = next) {
|
||||||
* Remove the first item in the list.
|
next = item.next;
|
||||||
* @returns {ListItem}
|
item.prev = null;
|
||||||
*/
|
item.next = null;
|
||||||
|
|
||||||
List.prototype.shift = function shift() {
|
|
||||||
const item = this.head;
|
|
||||||
|
|
||||||
if (!item)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
this.remove(item);
|
|
||||||
|
|
||||||
return item;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepend an item to the linked list (sets new head).
|
|
||||||
* @param {ListItem}
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
List.prototype.unshift = function unshift(item) {
|
|
||||||
return this.insert(null, item);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append an item to the linked list (sets new tail).
|
|
||||||
* @param {ListItem}
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
List.prototype.push = function push(item) {
|
|
||||||
return this.insert(this.tail, item);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the last item in the list.
|
|
||||||
* @returns {ListItem}
|
|
||||||
*/
|
|
||||||
|
|
||||||
List.prototype.pop = function pop() {
|
|
||||||
const item = this.tail;
|
|
||||||
|
|
||||||
if (!item)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
this.remove(item);
|
|
||||||
|
|
||||||
return item;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert item into the linked list.
|
|
||||||
* @private
|
|
||||||
* @param {ListItem|null} ref
|
|
||||||
* @param {ListItem} item
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
List.prototype.insert = function insert(ref, item) {
|
|
||||||
if (item.prev || item.next || item === this.head)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
assert(!item.prev);
|
|
||||||
assert(!item.next);
|
|
||||||
|
|
||||||
if (ref == null) {
|
|
||||||
if (!this.head) {
|
|
||||||
this.head = item;
|
|
||||||
this.tail = item;
|
|
||||||
} else {
|
|
||||||
this.head.prev = item;
|
|
||||||
item.next = this.head;
|
|
||||||
this.head = item;
|
|
||||||
}
|
}
|
||||||
this.size++;
|
|
||||||
|
assert(!item);
|
||||||
|
|
||||||
|
this.head = null;
|
||||||
|
this.tail = null;
|
||||||
|
this.size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the first item in the list.
|
||||||
|
* @returns {ListItem}
|
||||||
|
*/
|
||||||
|
|
||||||
|
shift() {
|
||||||
|
const item = this.head;
|
||||||
|
|
||||||
|
if (!item)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
this.remove(item);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepend an item to the linked list (sets new head).
|
||||||
|
* @param {ListItem}
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
unshift(item) {
|
||||||
|
return this.insert(null, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append an item to the linked list (sets new tail).
|
||||||
|
* @param {ListItem}
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
push(item) {
|
||||||
|
return this.insert(this.tail, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the last item in the list.
|
||||||
|
* @returns {ListItem}
|
||||||
|
*/
|
||||||
|
|
||||||
|
pop() {
|
||||||
|
const item = this.tail;
|
||||||
|
|
||||||
|
if (!item)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
this.remove(item);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert item into the linked list.
|
||||||
|
* @private
|
||||||
|
* @param {ListItem|null} ref
|
||||||
|
* @param {ListItem} item
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
insert(ref, item) {
|
||||||
|
if (item.prev || item.next || item === this.head)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
assert(!item.prev);
|
||||||
|
assert(!item.next);
|
||||||
|
|
||||||
|
if (ref == null) {
|
||||||
|
if (!this.head) {
|
||||||
|
this.head = item;
|
||||||
|
this.tail = item;
|
||||||
|
} else {
|
||||||
|
this.head.prev = item;
|
||||||
|
item.next = this.head;
|
||||||
|
this.head = item;
|
||||||
|
}
|
||||||
|
this.size += 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.next = ref.next;
|
||||||
|
item.prev = ref;
|
||||||
|
ref.next = item;
|
||||||
|
|
||||||
|
if (ref === this.tail)
|
||||||
|
this.tail = item;
|
||||||
|
|
||||||
|
this.size += 1;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.next = ref.next;
|
/**
|
||||||
item.prev = ref;
|
* Remove item from the linked list.
|
||||||
ref.next = item;
|
* @private
|
||||||
|
* @param {ListItem}
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
if (ref === this.tail)
|
remove(item) {
|
||||||
this.tail = item;
|
if (!item.prev && !item.next && item !== this.head)
|
||||||
|
return false;
|
||||||
|
|
||||||
this.size++;
|
if (item.prev)
|
||||||
|
item.prev.next = item.next;
|
||||||
|
|
||||||
return true;
|
if (item.next)
|
||||||
};
|
item.next.prev = item.prev;
|
||||||
|
|
||||||
/**
|
if (item === this.head)
|
||||||
* Remove item from the linked list.
|
this.head = item.next;
|
||||||
* @private
|
|
||||||
* @param {ListItem}
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
List.prototype.remove = function remove(item) {
|
if (item === this.tail)
|
||||||
if (!item.prev && !item.next && item !== this.head)
|
this.tail = item.prev || this.head;
|
||||||
return false;
|
|
||||||
|
|
||||||
if (item.prev)
|
if (!this.head)
|
||||||
item.prev.next = item.next;
|
assert(!this.tail);
|
||||||
|
|
||||||
if (item.next)
|
if (!this.tail)
|
||||||
item.next.prev = item.prev;
|
assert(!this.head);
|
||||||
|
|
||||||
if (item === this.head)
|
|
||||||
this.head = item.next;
|
|
||||||
|
|
||||||
if (item === this.tail)
|
|
||||||
this.tail = item.prev || this.head;
|
|
||||||
|
|
||||||
if (!this.head)
|
|
||||||
assert(!this.tail);
|
|
||||||
|
|
||||||
if (!this.tail)
|
|
||||||
assert(!this.head);
|
|
||||||
|
|
||||||
item.prev = null;
|
|
||||||
item.next = null;
|
|
||||||
|
|
||||||
this.size--;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace an item in-place.
|
|
||||||
* @param {ListItem} ref
|
|
||||||
* @param {ListItem} item
|
|
||||||
*/
|
|
||||||
|
|
||||||
List.prototype.replace = function replace(ref, item) {
|
|
||||||
if (ref.prev)
|
|
||||||
ref.prev.next = item;
|
|
||||||
|
|
||||||
if (ref.next)
|
|
||||||
ref.next.prev = item;
|
|
||||||
|
|
||||||
item.prev = ref.prev;
|
|
||||||
item.next = ref.next;
|
|
||||||
|
|
||||||
ref.next = null;
|
|
||||||
ref.prev = null;
|
|
||||||
|
|
||||||
if (this.head === ref)
|
|
||||||
this.head = item;
|
|
||||||
|
|
||||||
if (this.tail === ref)
|
|
||||||
this.tail = item;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Slice the list to an array of items.
|
|
||||||
* Will remove the items sliced.
|
|
||||||
* @param {Number?} total
|
|
||||||
* @returns {ListItem[]}
|
|
||||||
*/
|
|
||||||
|
|
||||||
List.prototype.slice = function slice(total) {
|
|
||||||
const items = [];
|
|
||||||
let item, next;
|
|
||||||
|
|
||||||
if (total == null)
|
|
||||||
total = -1;
|
|
||||||
|
|
||||||
for (item = this.head; item; item = next) {
|
|
||||||
next = item.next;
|
|
||||||
item.prev = null;
|
item.prev = null;
|
||||||
item.next = null;
|
item.next = null;
|
||||||
|
|
||||||
this.size--;
|
this.size -= 1;
|
||||||
|
|
||||||
items.push(item);
|
return true;
|
||||||
|
|
||||||
if (items.length === total)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next) {
|
/**
|
||||||
this.head = next;
|
* Replace an item in-place.
|
||||||
next.prev = null;
|
* @param {ListItem} ref
|
||||||
} else {
|
* @param {ListItem} item
|
||||||
this.head = null;
|
*/
|
||||||
this.tail = null;
|
|
||||||
|
replace(ref, item) {
|
||||||
|
if (ref.prev)
|
||||||
|
ref.prev.next = item;
|
||||||
|
|
||||||
|
if (ref.next)
|
||||||
|
ref.next.prev = item;
|
||||||
|
|
||||||
|
item.prev = ref.prev;
|
||||||
|
item.next = ref.next;
|
||||||
|
|
||||||
|
ref.next = null;
|
||||||
|
ref.prev = null;
|
||||||
|
|
||||||
|
if (this.head === ref)
|
||||||
|
this.head = item;
|
||||||
|
|
||||||
|
if (this.tail === ref)
|
||||||
|
this.tail = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
/**
|
||||||
};
|
* Slice the list to an array of items.
|
||||||
|
* Will remove the items sliced.
|
||||||
|
* @param {Number?} total
|
||||||
|
* @returns {ListItem[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
slice(total) {
|
||||||
|
if (total == null)
|
||||||
|
total = -1;
|
||||||
|
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
let next = null;
|
||||||
|
|
||||||
|
for (let item = this.head; item; item = next) {
|
||||||
|
next = item.next;
|
||||||
|
item.prev = null;
|
||||||
|
item.next = null;
|
||||||
|
|
||||||
|
this.size -= 1;
|
||||||
|
|
||||||
|
items.push(item);
|
||||||
|
|
||||||
|
if (items.length === total)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next) {
|
||||||
|
this.head = next;
|
||||||
|
next.prev = null;
|
||||||
|
} else {
|
||||||
|
this.head = null;
|
||||||
|
this.tail = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the list to an array of items.
|
||||||
|
* @returns {ListItem[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
toArray() {
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
for (let item = this.head; item; item = item.next)
|
||||||
|
items.push(item);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert the list to an array of items.
|
* List Item
|
||||||
* @returns {ListItem[]}
|
|
||||||
*/
|
|
||||||
|
|
||||||
List.prototype.toArray = function toArray() {
|
|
||||||
const items = [];
|
|
||||||
|
|
||||||
for (let item = this.head; item; item = item.next)
|
|
||||||
items.push(item);
|
|
||||||
|
|
||||||
return items;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an linked list item.
|
|
||||||
* @alias module:utils.ListItem
|
* @alias module:utils.ListItem
|
||||||
* @constructor
|
|
||||||
* @private
|
|
||||||
* @param {String} key
|
|
||||||
* @param {Object} value
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function ListItem(value) {
|
class ListItem {
|
||||||
this.next = null;
|
/**
|
||||||
this.prev = null;
|
* Create a list item.
|
||||||
this.value = value;
|
* @constructor
|
||||||
|
* @private
|
||||||
|
* @param {String} key
|
||||||
|
* @param {Object} value
|
||||||
|
*/
|
||||||
|
|
||||||
|
constructor(value) {
|
||||||
|
this.next = null;
|
||||||
|
this.prev = null;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@ -10,194 +10,196 @@
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a mutex lock for locking asynchronous object methods.
|
* Mutex Lock
|
||||||
* @alias module:utils.Lock
|
* @alias module:utils.Lock
|
||||||
* @constructor
|
|
||||||
* @param {Boolean?} named - Whether to
|
|
||||||
* maintain a map of queued jobs by job name.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function Lock(named) {
|
class Lock {
|
||||||
if (!(this instanceof Lock))
|
/**
|
||||||
return Lock.create(named);
|
* Create a lock.
|
||||||
|
* @constructor
|
||||||
|
* @param {Boolean?} named - Whether to
|
||||||
|
* maintain a map of queued jobs by job name.
|
||||||
|
*/
|
||||||
|
|
||||||
this.named = named === true;
|
constructor(named = false) {
|
||||||
|
this.named = named === true;
|
||||||
|
|
||||||
this.jobs = [];
|
this.jobs = [];
|
||||||
this.busy = false;
|
this.busy = false;
|
||||||
this.destroyed = false;
|
this.destroyed = false;
|
||||||
|
|
||||||
this.map = new Map();
|
this.map = new Map();
|
||||||
this.current = null;
|
this.current = null;
|
||||||
|
|
||||||
this.unlocker = this.unlock.bind(this);
|
this.unlocker = this.unlock.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a closure scoped lock.
|
||||||
|
* @param {Boolean?} named
|
||||||
|
* @returns {Function} Lock method.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static create(named) {
|
||||||
|
const lock = new Lock(named);
|
||||||
|
return function _lock(arg1, arg2) {
|
||||||
|
return lock.lock(arg1, arg2);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether the lock has a pending
|
||||||
|
* job or a job in progress (by name).
|
||||||
|
* @param {String} name
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
has(name) {
|
||||||
|
assert(this.named, 'Must use named jobs.');
|
||||||
|
|
||||||
|
if (this.current === name)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return this.pending(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether the lock has
|
||||||
|
* a pending job by name.
|
||||||
|
* @param {String} name
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
pending(name) {
|
||||||
|
assert(this.named, 'Must use named jobs.');
|
||||||
|
|
||||||
|
const count = this.map.get(name);
|
||||||
|
|
||||||
|
if (count == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock the parent object and all its methods
|
||||||
|
* which use the lock. Begin to queue calls.
|
||||||
|
* @param {String?} name - Job name.
|
||||||
|
* @param {Boolean?} force - Bypass the lock.
|
||||||
|
* @returns {Promise} - Returns {Function}, must be
|
||||||
|
* called once the method finishes executing in order
|
||||||
|
* to resolve the queue.
|
||||||
|
*/
|
||||||
|
|
||||||
|
lock(arg1, arg2) {
|
||||||
|
let name, force;
|
||||||
|
|
||||||
|
if (this.named) {
|
||||||
|
name = arg1 || null;
|
||||||
|
force = arg2 || false;
|
||||||
|
} else {
|
||||||
|
name = null;
|
||||||
|
force = arg1 || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.destroyed)
|
||||||
|
return Promise.reject(new Error('Lock is destroyed.'));
|
||||||
|
|
||||||
|
if (force) {
|
||||||
|
assert(this.busy);
|
||||||
|
return Promise.resolve(nop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.busy) {
|
||||||
|
if (name) {
|
||||||
|
const count = this.map.get(name) || 0;
|
||||||
|
this.map.set(name, count + 1);
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.jobs.push(new Job(resolve, reject, name));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.busy = true;
|
||||||
|
this.current = name;
|
||||||
|
|
||||||
|
return Promise.resolve(this.unlocker);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual unlock callback.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
unlock() {
|
||||||
|
assert(this.destroyed || this.busy);
|
||||||
|
|
||||||
|
this.busy = false;
|
||||||
|
this.current = null;
|
||||||
|
|
||||||
|
if (this.jobs.length === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
assert(!this.destroyed);
|
||||||
|
|
||||||
|
const job = this.jobs.shift();
|
||||||
|
|
||||||
|
if (job.name) {
|
||||||
|
let count = this.map.get(job.name);
|
||||||
|
assert(count > 0);
|
||||||
|
if (--count === 0)
|
||||||
|
this.map.delete(job.name);
|
||||||
|
else
|
||||||
|
this.map.set(job.name, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.busy = true;
|
||||||
|
this.current = job.name;
|
||||||
|
|
||||||
|
job.resolve(this.unlocker);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the lock. Purge all pending calls.
|
||||||
|
*/
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
assert(!this.destroyed, 'Lock is already destroyed.');
|
||||||
|
|
||||||
|
this.destroyed = true;
|
||||||
|
|
||||||
|
const jobs = this.jobs;
|
||||||
|
|
||||||
|
this.busy = false;
|
||||||
|
this.jobs = [];
|
||||||
|
this.map.clear();
|
||||||
|
this.current = null;
|
||||||
|
|
||||||
|
for (const job of jobs)
|
||||||
|
job.reject(new Error('Lock was destroyed.'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a closure scoped lock.
|
|
||||||
* @param {Boolean?} named
|
|
||||||
* @returns {Function} Lock method.
|
|
||||||
*/
|
|
||||||
|
|
||||||
Lock.create = function create(named) {
|
|
||||||
const lock = new Lock(named);
|
|
||||||
return function _lock(arg1, arg2) {
|
|
||||||
return lock.lock(arg1, arg2);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test whether the lock has a pending
|
|
||||||
* job or a job in progress (by name).
|
|
||||||
* @param {String} name
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
Lock.prototype.has = function has(name) {
|
|
||||||
assert(this.named, 'Must use named jobs.');
|
|
||||||
|
|
||||||
if (this.current === name)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
const count = this.map.get(name);
|
|
||||||
|
|
||||||
if (count == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return count > 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test whether the lock has
|
|
||||||
* a pending job by name.
|
|
||||||
* @param {String} name
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
Lock.prototype.hasPending = function hasPending(name) {
|
|
||||||
assert(this.named, 'Must use named jobs.');
|
|
||||||
|
|
||||||
const count = this.map.get(name);
|
|
||||||
|
|
||||||
if (count == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return count > 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lock the parent object and all its methods
|
|
||||||
* which use the lock. Begin to queue calls.
|
|
||||||
* @param {String?} name - Job name.
|
|
||||||
* @param {Boolean?} force - Bypass the lock.
|
|
||||||
* @returns {Promise} - Returns {Function}, must be
|
|
||||||
* called once the method finishes executing in order
|
|
||||||
* to resolve the queue.
|
|
||||||
*/
|
|
||||||
|
|
||||||
Lock.prototype.lock = function lock(arg1, arg2) {
|
|
||||||
let name, force;
|
|
||||||
|
|
||||||
if (this.named) {
|
|
||||||
name = arg1 || null;
|
|
||||||
force = arg2;
|
|
||||||
} else {
|
|
||||||
name = null;
|
|
||||||
force = arg1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.destroyed)
|
|
||||||
return Promise.reject(new Error('Lock is destroyed.'));
|
|
||||||
|
|
||||||
if (force) {
|
|
||||||
assert(this.busy);
|
|
||||||
return Promise.resolve(nop);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.busy) {
|
|
||||||
if (name) {
|
|
||||||
let count = this.map.get(name);
|
|
||||||
if (!count)
|
|
||||||
count = 0;
|
|
||||||
this.map.set(name, count + 1);
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.jobs.push(new Job(resolve, reject, name));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.busy = true;
|
|
||||||
this.current = name;
|
|
||||||
|
|
||||||
return Promise.resolve(this.unlocker);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The actual unlock callback.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Lock.prototype.unlock = function unlock() {
|
|
||||||
assert(this.destroyed || this.busy);
|
|
||||||
|
|
||||||
this.busy = false;
|
|
||||||
this.current = null;
|
|
||||||
|
|
||||||
if (this.jobs.length === 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
assert(!this.destroyed);
|
|
||||||
|
|
||||||
const job = this.jobs.shift();
|
|
||||||
|
|
||||||
if (job.name) {
|
|
||||||
let count = this.map.get(job.name);
|
|
||||||
assert(count > 0);
|
|
||||||
if (--count === 0)
|
|
||||||
this.map.delete(job.name);
|
|
||||||
else
|
|
||||||
this.map.set(job.name, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.busy = true;
|
|
||||||
this.current = job.name;
|
|
||||||
|
|
||||||
job.resolve(this.unlocker);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy the lock. Purge all pending calls.
|
|
||||||
*/
|
|
||||||
|
|
||||||
Lock.prototype.destroy = function destroy() {
|
|
||||||
assert(!this.destroyed, 'Lock is already destroyed.');
|
|
||||||
|
|
||||||
this.destroyed = true;
|
|
||||||
|
|
||||||
const jobs = this.jobs;
|
|
||||||
|
|
||||||
this.busy = false;
|
|
||||||
this.jobs = [];
|
|
||||||
this.map.clear();
|
|
||||||
this.current = null;
|
|
||||||
|
|
||||||
for (const job of jobs)
|
|
||||||
job.reject(new Error('Lock was destroyed.'));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lock Job
|
* Lock Job
|
||||||
* @constructor
|
|
||||||
* @ignore
|
* @ignore
|
||||||
* @param {Function} resolve
|
|
||||||
* @param {Function} reject
|
|
||||||
* @param {String?} name
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function Job(resolve, reject, name) {
|
class Job {
|
||||||
this.resolve = resolve;
|
/**
|
||||||
this.reject = reject;
|
* Create a lock job.
|
||||||
this.name = name || null;
|
* @constructor
|
||||||
|
* @param {Function} resolve
|
||||||
|
* @param {Function} reject
|
||||||
|
* @param {String?} name
|
||||||
|
*/
|
||||||
|
|
||||||
|
constructor(resolve, reject, name) {
|
||||||
|
this.resolve = resolve;
|
||||||
|
this.reject = reject;
|
||||||
|
this.name = name || null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
934
lib/utils/lru.js
934
lib/utils/lru.js
@ -10,489 +10,515 @@
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An LRU cache, used for caching {@link ChainEntry}s.
|
* LRU Cache
|
||||||
* @alias module:utils.LRU
|
* @alias module:utils.LRU
|
||||||
* @constructor
|
|
||||||
* @param {Number} capacity
|
|
||||||
* @param {Function?} getSize
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function LRU(capacity, getSize) {
|
class LRU {
|
||||||
if (!(this instanceof LRU))
|
/**
|
||||||
return new LRU(capacity, getSize);
|
* Create an LRU cache.
|
||||||
|
* @constructor
|
||||||
|
* @param {Number} capacity
|
||||||
|
* @param {Function?} getSize
|
||||||
|
*/
|
||||||
|
|
||||||
this.map = new Map();
|
constructor(capacity, getSize) {
|
||||||
this.size = 0;
|
this.map = new Map();
|
||||||
this.items = 0;
|
this.size = 0;
|
||||||
this.head = null;
|
this.items = 0;
|
||||||
this.tail = null;
|
this.head = null;
|
||||||
this.pending = null;
|
this.tail = null;
|
||||||
|
this.pending = null;
|
||||||
|
|
||||||
assert(typeof capacity === 'number', 'Capacity must be a number.');
|
assert(typeof capacity === 'number', 'Capacity must be a number.');
|
||||||
assert(capacity >= 0, 'Capacity cannot be negative.');
|
assert(capacity >= 0, 'Capacity cannot be negative.');
|
||||||
assert(!getSize || typeof getSize === 'function', 'Bad size callback.');
|
assert(!getSize || typeof getSize === 'function', 'Bad size callback.');
|
||||||
|
|
||||||
this.capacity = capacity;
|
this.capacity = capacity;
|
||||||
this.getSize = getSize;
|
this.getSize = getSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate size of an item.
|
||||||
|
* @private
|
||||||
|
* @param {LRUItem} item
|
||||||
|
* @returns {Number} Size.
|
||||||
|
*/
|
||||||
|
|
||||||
|
_getSize(item) {
|
||||||
|
if (this.getSize) {
|
||||||
|
const keySize = Math.floor(item.key.length * 1.375);
|
||||||
|
return 120 + keySize + this.getSize(item.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compact the LRU linked list.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
_compact() {
|
||||||
|
if (this.size <= this.capacity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let item = null;
|
||||||
|
let next = null;
|
||||||
|
|
||||||
|
for (item = this.head; item; item = next) {
|
||||||
|
if (this.size <= this.capacity)
|
||||||
|
break;
|
||||||
|
|
||||||
|
this.size -= this._getSize(item);
|
||||||
|
this.items -= 1;
|
||||||
|
this.map.delete(item.key);
|
||||||
|
|
||||||
|
next = item.next;
|
||||||
|
|
||||||
|
item.prev = null;
|
||||||
|
item.next = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
this.head = null;
|
||||||
|
this.tail = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.head = item;
|
||||||
|
item.prev = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the cache. Clear all items.
|
||||||
|
*/
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
let item, next;
|
||||||
|
|
||||||
|
for (item = this.head; item; item = next) {
|
||||||
|
this.map.delete(item.key);
|
||||||
|
this.items -= 1;
|
||||||
|
next = item.next;
|
||||||
|
item.prev = null;
|
||||||
|
item.next = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!item);
|
||||||
|
|
||||||
|
this.size = 0;
|
||||||
|
this.head = null;
|
||||||
|
this.tail = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an item to the cache.
|
||||||
|
* @param {String|Number} key
|
||||||
|
* @param {Object} value
|
||||||
|
*/
|
||||||
|
|
||||||
|
set(key, value) {
|
||||||
|
if (this.capacity === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
key = String(key);
|
||||||
|
|
||||||
|
let item = this.map.get(key);
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
this.size -= this._getSize(item);
|
||||||
|
item.value = value;
|
||||||
|
this.size += this._getSize(item);
|
||||||
|
this._removeList(item);
|
||||||
|
this._appendList(item);
|
||||||
|
this._compact();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item = new LRUItem(key, value);
|
||||||
|
|
||||||
|
this.map.set(key, item);
|
||||||
|
|
||||||
|
this._appendList(item);
|
||||||
|
|
||||||
|
this.size += this._getSize(item);
|
||||||
|
this.items += 1;
|
||||||
|
|
||||||
|
this._compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an item from the cache.
|
||||||
|
* @param {String|Number} key
|
||||||
|
* @returns {Object} Item.
|
||||||
|
*/
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
if (this.capacity === 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
key = String(key);
|
||||||
|
|
||||||
|
const item = this.map.get(key);
|
||||||
|
|
||||||
|
if (!item)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
this._removeList(item);
|
||||||
|
this._appendList(item);
|
||||||
|
|
||||||
|
return item.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether the cache contains a key.
|
||||||
|
* @param {String|Number} key
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
has(key) {
|
||||||
|
if (this.capacity === 0)
|
||||||
|
return false;
|
||||||
|
return this.map.has(String(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an item from the cache.
|
||||||
|
* @param {String|Number} key
|
||||||
|
* @returns {Boolean} Whether an item was removed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
remove(key) {
|
||||||
|
if (this.capacity === 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
key = String(key);
|
||||||
|
|
||||||
|
const item = this.map.get(key);
|
||||||
|
|
||||||
|
if (!item)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this.size -= this._getSize(item);
|
||||||
|
this.items -= 1;
|
||||||
|
|
||||||
|
this.map.delete(key);
|
||||||
|
|
||||||
|
this._removeList(item);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepend an item to the linked list (sets new head).
|
||||||
|
* @private
|
||||||
|
* @param {LRUItem}
|
||||||
|
*/
|
||||||
|
|
||||||
|
_prependList(item) {
|
||||||
|
this._insertList(null, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append an item to the linked list (sets new tail).
|
||||||
|
* @private
|
||||||
|
* @param {LRUItem}
|
||||||
|
*/
|
||||||
|
|
||||||
|
_appendList(item) {
|
||||||
|
this._insertList(this.tail, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert item into the linked list.
|
||||||
|
* @private
|
||||||
|
* @param {LRUItem|null} ref
|
||||||
|
* @param {LRUItem} item
|
||||||
|
*/
|
||||||
|
|
||||||
|
_insertList(ref, item) {
|
||||||
|
assert(!item.next);
|
||||||
|
assert(!item.prev);
|
||||||
|
|
||||||
|
if (ref == null) {
|
||||||
|
if (!this.head) {
|
||||||
|
this.head = item;
|
||||||
|
this.tail = item;
|
||||||
|
} else {
|
||||||
|
this.head.prev = item;
|
||||||
|
item.next = this.head;
|
||||||
|
this.head = item;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.next = ref.next;
|
||||||
|
item.prev = ref;
|
||||||
|
ref.next = item;
|
||||||
|
|
||||||
|
if (ref === this.tail)
|
||||||
|
this.tail = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove item from the linked list.
|
||||||
|
* @private
|
||||||
|
* @param {LRUItem}
|
||||||
|
*/
|
||||||
|
|
||||||
|
_removeList(item) {
|
||||||
|
if (item.prev)
|
||||||
|
item.prev.next = item.next;
|
||||||
|
|
||||||
|
if (item.next)
|
||||||
|
item.next.prev = item.prev;
|
||||||
|
|
||||||
|
if (item === this.head)
|
||||||
|
this.head = item.next;
|
||||||
|
|
||||||
|
if (item === this.tail)
|
||||||
|
this.tail = item.prev || this.head;
|
||||||
|
|
||||||
|
if (!this.head)
|
||||||
|
assert(!this.tail);
|
||||||
|
|
||||||
|
if (!this.tail)
|
||||||
|
assert(!this.head);
|
||||||
|
|
||||||
|
item.prev = null;
|
||||||
|
item.next = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect all keys in the cache, sorted by LRU.
|
||||||
|
* @returns {String[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
keys() {
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
for (let item = this.head; item; item = item.next) {
|
||||||
|
if (item === this.head)
|
||||||
|
assert(!item.prev);
|
||||||
|
if (!item.prev)
|
||||||
|
assert(item === this.head);
|
||||||
|
if (!item.next)
|
||||||
|
assert(item === this.tail);
|
||||||
|
items.push(item.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect all values in the cache, sorted by LRU.
|
||||||
|
* @returns {String[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
values() {
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
for (let item = this.head; item; item = item.next)
|
||||||
|
items.push(item.value);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the LRU cache to an array of items.
|
||||||
|
* @returns {Object[]}
|
||||||
|
*/
|
||||||
|
|
||||||
|
toArray() {
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
for (let item = this.head; item; item = item.next)
|
||||||
|
items.push(item);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an atomic batch for the lru
|
||||||
|
* (used for caching database writes).
|
||||||
|
* @returns {LRUBatch}
|
||||||
|
*/
|
||||||
|
|
||||||
|
batch() {
|
||||||
|
return new LRUBatch(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the pending batch.
|
||||||
|
*/
|
||||||
|
|
||||||
|
start() {
|
||||||
|
assert(!this.pending);
|
||||||
|
this.pending = this.batch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the pending batch.
|
||||||
|
*/
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
assert(this.pending);
|
||||||
|
this.pending.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop the pending batch.
|
||||||
|
*/
|
||||||
|
|
||||||
|
drop() {
|
||||||
|
assert(this.pending);
|
||||||
|
this.pending = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commit the pending batch.
|
||||||
|
*/
|
||||||
|
|
||||||
|
commit() {
|
||||||
|
assert(this.pending);
|
||||||
|
this.pending.commit();
|
||||||
|
this.pending = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push an item onto the pending batch.
|
||||||
|
* @param {String} key
|
||||||
|
* @param {Object} value
|
||||||
|
*/
|
||||||
|
|
||||||
|
push(key, value) {
|
||||||
|
assert(this.pending);
|
||||||
|
|
||||||
|
if (this.capacity === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.pending.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a removal onto the pending batch.
|
||||||
|
* @param {String} key
|
||||||
|
*/
|
||||||
|
|
||||||
|
unpush(key) {
|
||||||
|
assert(this.pending);
|
||||||
|
|
||||||
|
if (this.capacity === 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.pending.remove(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate size of an item.
|
* LRU Item
|
||||||
* @private
|
|
||||||
* @param {LRUItem} item
|
|
||||||
* @returns {Number} Size.
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype._getSize = function _getSize(item) {
|
|
||||||
if (this.getSize) {
|
|
||||||
const keySize = Math.floor(item.key.length * 1.375);
|
|
||||||
return 120 + keySize + this.getSize(item.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compact the LRU linked list.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype._compact = function _compact() {
|
|
||||||
if (this.size <= this.capacity)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let item, next;
|
|
||||||
for (item = this.head; item; item = next) {
|
|
||||||
if (this.size <= this.capacity)
|
|
||||||
break;
|
|
||||||
this.size -= this._getSize(item);
|
|
||||||
this.items--;
|
|
||||||
this.map.delete(item.key);
|
|
||||||
next = item.next;
|
|
||||||
item.prev = null;
|
|
||||||
item.next = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
this.head = null;
|
|
||||||
this.tail = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.head = item;
|
|
||||||
item.prev = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the cache. Clear all items.
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.reset = function reset() {
|
|
||||||
let item, next;
|
|
||||||
|
|
||||||
for (item = this.head; item; item = next) {
|
|
||||||
this.map.delete(item.key);
|
|
||||||
this.items--;
|
|
||||||
next = item.next;
|
|
||||||
item.prev = null;
|
|
||||||
item.next = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(!item);
|
|
||||||
|
|
||||||
this.size = 0;
|
|
||||||
this.head = null;
|
|
||||||
this.tail = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an item to the cache.
|
|
||||||
* @param {String|Number} key
|
|
||||||
* @param {Object} value
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.set = function set(key, value) {
|
|
||||||
if (this.capacity === 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
key = String(key);
|
|
||||||
|
|
||||||
let item = this.map.get(key);
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
this.size -= this._getSize(item);
|
|
||||||
item.value = value;
|
|
||||||
this.size += this._getSize(item);
|
|
||||||
this._removeList(item);
|
|
||||||
this._appendList(item);
|
|
||||||
this._compact();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
item = new LRUItem(key, value);
|
|
||||||
|
|
||||||
this.map.set(key, item);
|
|
||||||
|
|
||||||
this._appendList(item);
|
|
||||||
|
|
||||||
this.size += this._getSize(item);
|
|
||||||
this.items++;
|
|
||||||
|
|
||||||
this._compact();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve an item from the cache.
|
|
||||||
* @param {String|Number} key
|
|
||||||
* @returns {Object} Item.
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.get = function get(key) {
|
|
||||||
if (this.capacity === 0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
key = String(key);
|
|
||||||
|
|
||||||
const item = this.map.get(key);
|
|
||||||
|
|
||||||
if (!item)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
this._removeList(item);
|
|
||||||
this._appendList(item);
|
|
||||||
|
|
||||||
return item.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test whether the cache contains a key.
|
|
||||||
* @param {String|Number} key
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.has = function has(key) {
|
|
||||||
if (this.capacity === 0)
|
|
||||||
return false;
|
|
||||||
return this.map.has(String(key));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an item from the cache.
|
|
||||||
* @param {String|Number} key
|
|
||||||
* @returns {Boolean} Whether an item was removed.
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.remove = function remove(key) {
|
|
||||||
if (this.capacity === 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
key = String(key);
|
|
||||||
|
|
||||||
const item = this.map.get(key);
|
|
||||||
|
|
||||||
if (!item)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
this.size -= this._getSize(item);
|
|
||||||
this.items--;
|
|
||||||
|
|
||||||
this.map.delete(key);
|
|
||||||
|
|
||||||
this._removeList(item);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepend an item to the linked list (sets new head).
|
|
||||||
* @private
|
|
||||||
* @param {LRUItem}
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype._prependList = function _prependList(item) {
|
|
||||||
this._insertList(null, item);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append an item to the linked list (sets new tail).
|
|
||||||
* @private
|
|
||||||
* @param {LRUItem}
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype._appendList = function _appendList(item) {
|
|
||||||
this._insertList(this.tail, item);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert item into the linked list.
|
|
||||||
* @private
|
|
||||||
* @param {LRUItem|null} ref
|
|
||||||
* @param {LRUItem} item
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype._insertList = function _insertList(ref, item) {
|
|
||||||
assert(!item.next);
|
|
||||||
assert(!item.prev);
|
|
||||||
|
|
||||||
if (ref == null) {
|
|
||||||
if (!this.head) {
|
|
||||||
this.head = item;
|
|
||||||
this.tail = item;
|
|
||||||
} else {
|
|
||||||
this.head.prev = item;
|
|
||||||
item.next = this.head;
|
|
||||||
this.head = item;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
item.next = ref.next;
|
|
||||||
item.prev = ref;
|
|
||||||
ref.next = item;
|
|
||||||
|
|
||||||
if (ref === this.tail)
|
|
||||||
this.tail = item;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove item from the linked list.
|
|
||||||
* @private
|
|
||||||
* @param {LRUItem}
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype._removeList = function _removeList(item) {
|
|
||||||
if (item.prev)
|
|
||||||
item.prev.next = item.next;
|
|
||||||
|
|
||||||
if (item.next)
|
|
||||||
item.next.prev = item.prev;
|
|
||||||
|
|
||||||
if (item === this.head)
|
|
||||||
this.head = item.next;
|
|
||||||
|
|
||||||
if (item === this.tail)
|
|
||||||
this.tail = item.prev || this.head;
|
|
||||||
|
|
||||||
if (!this.head)
|
|
||||||
assert(!this.tail);
|
|
||||||
|
|
||||||
if (!this.tail)
|
|
||||||
assert(!this.head);
|
|
||||||
|
|
||||||
item.prev = null;
|
|
||||||
item.next = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collect all keys in the cache, sorted by LRU.
|
|
||||||
* @returns {String[]}
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.keys = function keys() {
|
|
||||||
const items = [];
|
|
||||||
|
|
||||||
for (let item = this.head; item; item = item.next) {
|
|
||||||
if (item === this.head)
|
|
||||||
assert(!item.prev);
|
|
||||||
if (!item.prev)
|
|
||||||
assert(item === this.head);
|
|
||||||
if (!item.next)
|
|
||||||
assert(item === this.tail);
|
|
||||||
items.push(item.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collect all values in the cache, sorted by LRU.
|
|
||||||
* @returns {String[]}
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.values = function values() {
|
|
||||||
const items = [];
|
|
||||||
|
|
||||||
for (let item = this.head; item; item = item.next)
|
|
||||||
items.push(item.value);
|
|
||||||
|
|
||||||
return items;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert the LRU cache to an array of items.
|
|
||||||
* @returns {Object[]}
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.toArray = function toArray() {
|
|
||||||
const items = [];
|
|
||||||
|
|
||||||
for (let item = this.head; item; item = item.next)
|
|
||||||
items.push(item);
|
|
||||||
|
|
||||||
return items;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an atomic batch for the lru
|
|
||||||
* (used for caching database writes).
|
|
||||||
* @returns {LRUBatch}
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.batch = function batch() {
|
|
||||||
return new LRUBatch(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the pending batch.
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.start = function start() {
|
|
||||||
assert(!this.pending);
|
|
||||||
this.pending = this.batch();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the pending batch.
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.clear = function clear() {
|
|
||||||
assert(this.pending);
|
|
||||||
this.pending.clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drop the pending batch.
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.drop = function drop() {
|
|
||||||
assert(this.pending);
|
|
||||||
this.pending = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit the pending batch.
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.commit = function commit() {
|
|
||||||
assert(this.pending);
|
|
||||||
this.pending.commit();
|
|
||||||
this.pending = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Push an item onto the pending batch.
|
|
||||||
* @param {String} key
|
|
||||||
* @param {Object} value
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.push = function push(key, value) {
|
|
||||||
assert(this.pending);
|
|
||||||
|
|
||||||
if (this.capacity === 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.pending.set(key, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Push a removal onto the pending batch.
|
|
||||||
* @param {String} key
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRU.prototype.unpush = function unpush(key) {
|
|
||||||
assert(this.pending);
|
|
||||||
|
|
||||||
if (this.capacity === 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.pending.remove(key);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an LRU item.
|
|
||||||
* @alias module:utils.LRUItem
|
* @alias module:utils.LRUItem
|
||||||
* @constructor
|
|
||||||
* @private
|
|
||||||
* @param {String} key
|
|
||||||
* @param {Object} value
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function LRUItem(key, value) {
|
class LRUItem {
|
||||||
this.key = key;
|
/**
|
||||||
this.value = value;
|
* Create an LRU item.
|
||||||
this.next = null;
|
* @constructor
|
||||||
this.prev = null;
|
* @private
|
||||||
|
* @param {String} key
|
||||||
|
* @param {Object} value
|
||||||
|
*/
|
||||||
|
|
||||||
|
constructor(key, value) {
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
this.next = null;
|
||||||
|
this.prev = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LRU Batch
|
* LRU Batch
|
||||||
* @alias module:utils.LRUBatch
|
* @alias module:utils.LRUBatch
|
||||||
* @constructor
|
|
||||||
* @param {LRU} lru
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function LRUBatch(lru) {
|
class LRUBatch {
|
||||||
this.lru = lru;
|
/**
|
||||||
this.ops = [];
|
* Create an LRU batch.
|
||||||
}
|
* @constructor
|
||||||
|
* @param {LRU} lru
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
constructor(lru) {
|
||||||
* Push an item onto the batch.
|
this.lru = lru;
|
||||||
* @param {String} key
|
this.ops = [];
|
||||||
* @param {Object} value
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRUBatch.prototype.set = function set(key, value) {
|
|
||||||
this.ops.push(new LRUOp(false, key, value));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Push a removal onto the batch.
|
|
||||||
* @param {String} key
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRUBatch.prototype.remove = function remove(key) {
|
|
||||||
this.ops.push(new LRUOp(true, key, null));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the batch.
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRUBatch.prototype.clear = function clear() {
|
|
||||||
this.ops.length = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit the batch.
|
|
||||||
*/
|
|
||||||
|
|
||||||
LRUBatch.prototype.commit = function commit() {
|
|
||||||
for (const op of this.ops) {
|
|
||||||
if (op.remove) {
|
|
||||||
this.lru.remove(op.key);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this.lru.set(op.key, op.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ops.length = 0;
|
/**
|
||||||
};
|
* Push an item onto the batch.
|
||||||
|
* @param {String} key
|
||||||
|
* @param {Object} value
|
||||||
|
*/
|
||||||
|
|
||||||
|
set(key, value) {
|
||||||
|
this.ops.push(new LRUOp(false, key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a removal onto the batch.
|
||||||
|
* @param {String} key
|
||||||
|
*/
|
||||||
|
|
||||||
|
remove(key) {
|
||||||
|
this.ops.push(new LRUOp(true, key, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the batch.
|
||||||
|
*/
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.ops.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commit the batch.
|
||||||
|
*/
|
||||||
|
|
||||||
|
commit() {
|
||||||
|
for (const op of this.ops) {
|
||||||
|
if (op.remove) {
|
||||||
|
this.lru.remove(op.key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.lru.set(op.key, op.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ops.length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LRU Op
|
* LRU Op
|
||||||
* @alias module:utils.LRUOp
|
* @alias module:utils.LRUOp
|
||||||
* @constructor
|
|
||||||
* @private
|
* @private
|
||||||
* @param {Boolean} remove
|
|
||||||
* @param {String} key
|
|
||||||
* @param {Object} value
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function LRUOp(remove, key, value) {
|
class LRUOp {
|
||||||
this.remove = remove;
|
/**
|
||||||
this.key = key;
|
* Create an LRU op.
|
||||||
this.value = value;
|
* @constructor
|
||||||
|
* @param {Boolean} remove
|
||||||
|
* @param {String} key
|
||||||
|
* @param {Object} value
|
||||||
|
*/
|
||||||
|
|
||||||
|
constructor(remove, key, value) {
|
||||||
|
this.remove = remove;
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@ -10,154 +10,162 @@
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a mutex lock for locking asynchronous object methods.
|
* Mapped Lock
|
||||||
* Locks methods according to passed-in key.
|
|
||||||
* @alias module:utils.MappedLock
|
* @alias module:utils.MappedLock
|
||||||
* @constructor
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function MappedLock() {
|
class MappedLock {
|
||||||
if (!(this instanceof MappedLock))
|
/**
|
||||||
return MappedLock.create();
|
* Create a mapped lock.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
|
||||||
this.jobs = new Map();
|
constructor() {
|
||||||
this.busy = new Set();
|
this.jobs = new Map();
|
||||||
this.destroyed = false;
|
this.busy = new Set();
|
||||||
|
this.destroyed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a closure scoped lock.
|
||||||
|
* @returns {Function} Lock method.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static create() {
|
||||||
|
const lock = new MappedLock();
|
||||||
|
return function _lock(key, force) {
|
||||||
|
return lock.lock(key, force);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether the lock has a pending
|
||||||
|
* job or a job in progress (by name).
|
||||||
|
* @param {String} name
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
has(name) {
|
||||||
|
return this.busy.has(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether the lock has
|
||||||
|
* a pending job by name.
|
||||||
|
* @param {String} name
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
|
||||||
|
pending(name) {
|
||||||
|
return this.jobs.has(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock the parent object and all its methods
|
||||||
|
* which use the lock with a specified key.
|
||||||
|
* Begin to queue calls.
|
||||||
|
* @param {String|Number} key
|
||||||
|
* @param {Boolean} [force=false] - Force a call.
|
||||||
|
* @returns {Promise} - Returns {Function}, must be
|
||||||
|
* called once the method finishes executing in order
|
||||||
|
* to resolve the queue.
|
||||||
|
*/
|
||||||
|
|
||||||
|
lock(key, force = false) {
|
||||||
|
if (this.destroyed)
|
||||||
|
return Promise.reject(new Error('Lock is destroyed.'));
|
||||||
|
|
||||||
|
if (key == null)
|
||||||
|
return Promise.resolve(nop);
|
||||||
|
|
||||||
|
if (force) {
|
||||||
|
assert(this.busy.has(key));
|
||||||
|
return Promise.resolve(nop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.busy.has(key)) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!this.jobs.has(key))
|
||||||
|
this.jobs.set(key, []);
|
||||||
|
this.jobs.get(key).push(new Job(resolve, reject));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.busy.add(key);
|
||||||
|
|
||||||
|
return Promise.resolve(this.unlock(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an unlock callback.
|
||||||
|
* @private
|
||||||
|
* @param {String} key
|
||||||
|
* @returns {Function} Unlocker.
|
||||||
|
*/
|
||||||
|
|
||||||
|
unlock(key) {
|
||||||
|
const self = this;
|
||||||
|
return function unlocker() {
|
||||||
|
const jobs = self.jobs.get(key);
|
||||||
|
|
||||||
|
assert(self.destroyed || self.busy.has(key));
|
||||||
|
self.busy.delete(key);
|
||||||
|
|
||||||
|
if (!jobs)
|
||||||
|
return;
|
||||||
|
|
||||||
|
assert(!self.destroyed);
|
||||||
|
|
||||||
|
const job = jobs.shift();
|
||||||
|
assert(job);
|
||||||
|
|
||||||
|
if (jobs.length === 0)
|
||||||
|
self.jobs.delete(key);
|
||||||
|
|
||||||
|
self.busy.add(key);
|
||||||
|
|
||||||
|
job.resolve(unlocker);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the lock. Purge all pending calls.
|
||||||
|
*/
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
assert(!this.destroyed, 'Lock is already destroyed.');
|
||||||
|
|
||||||
|
const map = this.jobs;
|
||||||
|
|
||||||
|
this.destroyed = true;
|
||||||
|
|
||||||
|
this.jobs = new Map();
|
||||||
|
this.busy = new Map();
|
||||||
|
|
||||||
|
for (const jobs of map.values()) {
|
||||||
|
for (const job of jobs)
|
||||||
|
job.reject(new Error('Lock was destroyed.'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a closure scoped lock.
|
|
||||||
* @returns {Function} Lock method.
|
|
||||||
*/
|
|
||||||
|
|
||||||
MappedLock.create = function create() {
|
|
||||||
const lock = new MappedLock();
|
|
||||||
return function _lock(key, force) {
|
|
||||||
return lock.lock(key, force);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test whether the lock has a pending
|
|
||||||
* job or a job in progress (by name).
|
|
||||||
* @param {String} name
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
MappedLock.prototype.has = function has(name) {
|
|
||||||
return this.busy.has(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test whether the lock has
|
|
||||||
* a pending job by name.
|
|
||||||
* @param {String} name
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
MappedLock.prototype.hasPending = function hasPending(name) {
|
|
||||||
return this.jobs.has(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lock the parent object and all its methods
|
|
||||||
* which use the lock with a specified key.
|
|
||||||
* Begin to queue calls.
|
|
||||||
* @param {String|Number} key
|
|
||||||
* @param {Boolean?} force - Force a call.
|
|
||||||
* @returns {Promise} - Returns {Function}, must be
|
|
||||||
* called once the method finishes executing in order
|
|
||||||
* to resolve the queue.
|
|
||||||
*/
|
|
||||||
|
|
||||||
MappedLock.prototype.lock = function lock(key, force) {
|
|
||||||
if (this.destroyed)
|
|
||||||
return Promise.reject(new Error('Lock is destroyed.'));
|
|
||||||
|
|
||||||
if (key == null)
|
|
||||||
return Promise.resolve(nop);
|
|
||||||
|
|
||||||
if (force) {
|
|
||||||
assert(this.busy.has(key));
|
|
||||||
return Promise.resolve(nop);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.busy.has(key)) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (!this.jobs.has(key))
|
|
||||||
this.jobs.set(key, []);
|
|
||||||
this.jobs.get(key).push(new Job(resolve, reject));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.busy.add(key);
|
|
||||||
|
|
||||||
return Promise.resolve(this.unlock(key));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an unlock callback.
|
|
||||||
* @private
|
|
||||||
* @param {String} key
|
|
||||||
* @returns {Function} Unlocker.
|
|
||||||
*/
|
|
||||||
|
|
||||||
MappedLock.prototype.unlock = function unlock(key) {
|
|
||||||
const self = this;
|
|
||||||
return function unlocker() {
|
|
||||||
const jobs = self.jobs.get(key);
|
|
||||||
|
|
||||||
assert(self.destroyed || self.busy.has(key));
|
|
||||||
self.busy.delete(key);
|
|
||||||
|
|
||||||
if (!jobs)
|
|
||||||
return;
|
|
||||||
|
|
||||||
assert(!self.destroyed);
|
|
||||||
|
|
||||||
const job = jobs.shift();
|
|
||||||
assert(job);
|
|
||||||
|
|
||||||
if (jobs.length === 0)
|
|
||||||
self.jobs.delete(key);
|
|
||||||
|
|
||||||
self.busy.add(key);
|
|
||||||
|
|
||||||
job.resolve(unlocker);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy the lock. Purge all pending calls.
|
|
||||||
*/
|
|
||||||
|
|
||||||
MappedLock.prototype.destroy = function destroy() {
|
|
||||||
assert(!this.destroyed, 'Lock is already destroyed.');
|
|
||||||
|
|
||||||
const map = this.jobs;
|
|
||||||
|
|
||||||
this.destroyed = true;
|
|
||||||
|
|
||||||
this.jobs = new Map();
|
|
||||||
this.busy = new Map();
|
|
||||||
|
|
||||||
for (const jobs of map.values()) {
|
|
||||||
for (const job of jobs)
|
|
||||||
job.reject(new Error('Lock was destroyed.'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lock Job
|
* Lock Job
|
||||||
* @constructor
|
|
||||||
* @ignore
|
* @ignore
|
||||||
* @param {Function} resolve
|
|
||||||
* @param {Function} reject
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function Job(resolve, reject) {
|
class Job {
|
||||||
this.resolve = resolve;
|
/**
|
||||||
this.reject = reject;
|
* Create a lock job.
|
||||||
|
* @constructor
|
||||||
|
* @param {Function} resolve
|
||||||
|
* @param {Function} reject
|
||||||
|
*/
|
||||||
|
|
||||||
|
constructor(resolve, reject) {
|
||||||
|
this.resolve = resolve;
|
||||||
|
this.reject = reject;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@ -10,210 +10,209 @@ const assert = require('assert');
|
|||||||
const AsyncEmitter = require('../utils/asyncemitter');
|
const AsyncEmitter = require('../utils/asyncemitter');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NodeClient
|
* Node Client
|
||||||
* Sort of a fake local client for separation of concerns.
|
|
||||||
* @alias module:node.NodeClient
|
* @alias module:node.NodeClient
|
||||||
* @constructor
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function NodeClient(node) {
|
class NodeClient extends AsyncEmitter {
|
||||||
if (!(this instanceof NodeClient))
|
/**
|
||||||
return new NodeClient(node);
|
* Create a node client.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
|
||||||
AsyncEmitter.call(this);
|
constructor(node) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.node = node;
|
this.node = node;
|
||||||
this.network = node.network;
|
this.network = node.network;
|
||||||
this.filter = null;
|
this.filter = null;
|
||||||
this.opened = false;
|
this.opened = false;
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the client.
|
||||||
|
*/
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.node.on('connect', (entry, block) => {
|
||||||
|
if (!this.opened)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.emit('block connect', entry, block.txs);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.node.on('disconnect', (entry, block) => {
|
||||||
|
if (!this.opened)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.emit('block disconnect', entry);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.node.on('tx', (tx) => {
|
||||||
|
if (!this.opened)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.emit('tx', tx);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.node.on('reset', (tip) => {
|
||||||
|
if (!this.opened)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.emit('chain reset', tip);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the client.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async open(options) {
|
||||||
|
assert(!this.opened, 'NodeClient is already open.');
|
||||||
|
this.opened = true;
|
||||||
|
setImmediate(() => this.emit('connect'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the client.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
assert(this.opened, 'NodeClient is not open.');
|
||||||
|
this.opened = false;
|
||||||
|
setImmediate(() => this.emit('disconnect'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener.
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} handler
|
||||||
|
*/
|
||||||
|
|
||||||
|
bind(type, handler) {
|
||||||
|
return this.on(type, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener.
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} handler
|
||||||
|
*/
|
||||||
|
|
||||||
|
hook(type, handler) {
|
||||||
|
return this.on(type, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get chain tip.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async getTip() {
|
||||||
|
return this.node.chain.tip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get chain entry.
|
||||||
|
* @param {Hash} hash
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async getEntry(hash) {
|
||||||
|
const entry = await this.node.chain.getEntry(hash);
|
||||||
|
|
||||||
|
if (!entry)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (!await this.node.chain.isMainChain(entry))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a transaction. Do not wait for promise.
|
||||||
|
* @param {TX} tx
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async send(tx) {
|
||||||
|
this.node.relay(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set bloom filter.
|
||||||
|
* @param {Bloom} filter
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async setFilter(filter) {
|
||||||
|
this.filter = filter;
|
||||||
|
this.node.pool.setFilter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add data to filter.
|
||||||
|
* @param {Buffer} data
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async addFilter(data) {
|
||||||
|
this.node.pool.queueFilterLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset filter.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async resetFilter() {
|
||||||
|
this.node.pool.queueFilterLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Esimate smart fee.
|
||||||
|
* @param {Number?} blocks
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async estimateFee(blocks) {
|
||||||
|
if (!this.node.fees)
|
||||||
|
return this.network.feeRate;
|
||||||
|
|
||||||
|
return this.node.fees.estimateFee(blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get hash range.
|
||||||
|
* @param {Number} start
|
||||||
|
* @param {Number} end
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async getHashes(start = -1, end = -1) {
|
||||||
|
return this.node.chain.getHashes(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rescan for any missed transactions.
|
||||||
|
* @param {Number|Hash} start - Start block.
|
||||||
|
* @param {Bloom} filter
|
||||||
|
* @param {Function} iter - Iterator.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
|
||||||
|
async rescan(start) {
|
||||||
|
return this.node.chain.scan(start, this.filter, (entry, txs) => {
|
||||||
|
return this.emitAsync('block rescan', entry, txs);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.setPrototypeOf(NodeClient.prototype, AsyncEmitter.prototype);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the client.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.init = function init() {
|
|
||||||
this.node.on('connect', (entry, block) => {
|
|
||||||
if (!this.opened)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.emit('block connect', entry, block.txs);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.node.on('disconnect', (entry, block) => {
|
|
||||||
if (!this.opened)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.emit('block disconnect', entry);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.node.on('tx', (tx) => {
|
|
||||||
if (!this.opened)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.emit('tx', tx);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.node.on('reset', (tip) => {
|
|
||||||
if (!this.opened)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.emit('chain reset', tip);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the client.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.open = async function open(options) {
|
|
||||||
assert(!this.opened, 'NodeClient is already open.');
|
|
||||||
this.opened = true;
|
|
||||||
setImmediate(() => this.emit('connect'));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the client.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.close = async function close() {
|
|
||||||
assert(this.opened, 'NodeClient is not open.');
|
|
||||||
this.opened = false;
|
|
||||||
setImmediate(() => this.emit('disconnect'));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a listener.
|
|
||||||
* @param {String} type
|
|
||||||
* @param {Function} handler
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.bind = function bind(type, handler) {
|
|
||||||
return this.on(type, handler);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a listener.
|
|
||||||
* @param {String} type
|
|
||||||
* @param {Function} handler
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.hook = function hook(type, handler) {
|
|
||||||
return this.on(type, handler);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get chain tip.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.getTip = async function getTip() {
|
|
||||||
return this.node.chain.tip;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get chain entry.
|
|
||||||
* @param {Hash} hash
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.getEntry = async function getEntry(hash) {
|
|
||||||
const entry = await this.node.chain.getEntry(hash);
|
|
||||||
|
|
||||||
if (!entry)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (!await this.node.chain.isMainChain(entry))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a transaction. Do not wait for promise.
|
|
||||||
* @param {TX} tx
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.send = async function send(tx) {
|
|
||||||
this.node.relay(tx);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set bloom filter.
|
|
||||||
* @param {Bloom} filter
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.setFilter = async function setFilter(filter) {
|
|
||||||
this.filter = filter;
|
|
||||||
this.node.pool.setFilter(filter);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add data to filter.
|
|
||||||
* @param {Buffer} data
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.addFilter = async function addFilter(data) {
|
|
||||||
this.node.pool.queueFilterLoad();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset filter.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.resetFilter = async function resetFilter() {
|
|
||||||
this.node.pool.queueFilterLoad();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Esimate smart fee.
|
|
||||||
* @param {Number?} blocks
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.estimateFee = async function estimateFee(blocks) {
|
|
||||||
if (!this.node.fees)
|
|
||||||
return this.network.feeRate;
|
|
||||||
|
|
||||||
return this.node.fees.estimateFee(blocks);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get hash range.
|
|
||||||
* @param {Number} start
|
|
||||||
* @param {Number} end
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.getHashes = async function getHashes(start = -1, end = -1) {
|
|
||||||
return this.node.chain.getHashes(start, end);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rescan for any missed transactions.
|
|
||||||
* @param {Number|Hash} start - Start block.
|
|
||||||
* @param {Bloom} filter
|
|
||||||
* @param {Function} iter - Iterator.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
|
|
||||||
NodeClient.prototype.rescan = async function rescan(start) {
|
|
||||||
return this.node.chain.scan(start, this.filter, (entry, txs) => {
|
|
||||||
return this.emitAsync('block rescan', entry, txs);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Expose
|
* Expose
|
||||||
*/
|
*/
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user