<commit_before>"use strict";

/** @enum {string}
 */
var Priority = {
    /**
     * A lock with an UNSPECIFIED priority will try to acquire the locks in the order that they arrived
     */
    UNSPECIFIED: 'unspecified',

    /**
     * A lock with a READ priority will grant priority to read-locks,
     * meaning that if there's a pending write lock, new read locks will still be acquired.
     */
    READ: 'read',

    /**
     * A lock with a WRITE priority will grant priority to read-locks,
     * meaning that if there's a pending write lock, new read locks will not be acquired.
     */
    WRITE: 'write'
};

/**
 * Normalizes a priority value
 * @param {Priority} priority
 * @returns {Priority}
 */
var getValidPriority = function (priority) {

    if (priority !== Priority.READ &&
        priority !== Priority.WRITE &&
        priority !== Priority.UNSPECIFIED) {
        return Priority.UNSPECIFIED;
    }

    return priority;

};

/**
 * @class
 */
var MemoryLock = function ctor () {
    if (!(this instanceof ctor)) {
        var instance = Object.create(MemoryLock.prototype);
        (typeof this.init === 'function') && this.init.apply(this, args);
        return instance;
    }
    (typeof this.init === 'function') && this.init.apply(this, arguments);
};

/**
 * @public
 * @param {Object?} options
 * @param {String?} options.name The unique name for the lock
 * @param {Priority?} options.priority Priority policy, enum available in MemoryLock.Priority (UNSPECIFIED, READ, WRITE)
 */
MemoryLock.prototype.init = function (options) {

    var that = this;
    options = options || {};

    that.__name = options.name || null;
    var priority = that.__priority = getValidPriority(options.priority);

    that.__hasSplitLists = priority == Priority.READ || priority == Priority.WRITE;

    if (that.__hasSplitLists) {
        /** @member {Array?} */
        that.__readWaiters = [];

        /** @member {Array?} */
        that.__writeWaiters = [];
    } else {
        /** @member {Array?} */
        that.__waiters = [];
    }

    /** @member {Number} */
    that.__readLocks = 0;

    /** @member {Boolean} */
    that.__hasWriteLock = false;

    /** @member {Number} */
    that.__waitingWrite = 0;

    /** @member {Number} */
    that.__waitingRead = 0;

};

/**
 * Returns the relevant waiters list
 * @private
 * @param {Boolean} isWrite is this for a write lock or a read lock?
 * @returns {*}
 */
MemoryLock.prototype._getWaiterList = function (isWrite) {
    return this.__hasSplitLists ? (isWrite ? this.__writeWaiters : this.__readWaiters) : this.__waiters;
};

/**
 * This handles the "no" situation, creating the timeout if necessary or calling the callback
 * @private
 * @param timeout
 * @param callback
 * @param isWrite
 */
MemoryLock.prototype._handleNoLock = function (timeout, callback, isWrite) {

    var that = this;

    if (timeout !== 0) {
        var waiter = { cb: callback, w: isWrite, t: Date.now() };

        // Add this lock to the waiting list

        that._getWaiterList(isWrite).push(waiter);

        if (isWrite) {
            that.__waitingWrite++;
        } else {
            that.__waitingRead++;
        }

        // Create the timeout for this waiting lock

        if (timeout > 0) {
            waiter.to = setTimeout(function() {

                // Remove it from the waiter list
                that._getWaiterList(isWrite).remove(waiter);

                if (isWrite) {
                    that.__waitingWrite--;
                } else {
                    that.__waitingRead--;
                }

                // We cannot allow an exception here. We'll postpone them until after the lock's logic is done.
                var throwable = null;

                if (waiter.cb) {
                    try {
                        // Call with a global `this` and a 'timeout' error
                        waiter.cb.call(undefined, 'timeout');
                    } catch (e) {
                        throwable = e;
                    }
                }

                // It could be a write lock that has failed, and now read locks can be acquired...
                that._acquireWaitingLock();

                // Re-throw any error that came from the callback
                if (throwable) {
                    throw throwable;
                }

            }, timeout);
        }

    } else {

        if (callback) {
            // Call with a global `this` and a 'timeout' error
            callback.call('timeout');
        }

    }

};

/**
 * Looks for the next lock to acquire if any are waiting
 * @private
 */
MemoryLock.prototype._acquireWaitingLock = function () {

    var that = this;
    var waiter;

    if (that.__priority === Priority.READ) {

        // Priority for waiting readers
        if (that.__readWaiters.length) {

            // We can read-lock only if there are no write locks
            if (that.__hasWriteLock === false) {
                waiter = that.__readWaiters.shift();
                that.__waitingRead--;
            }

        } else if (that.__writeWaiters.length) {

            // Writers require exclusive lock
            if (that.__readLocks === 0 && that.__hasWriteLock === false) {
                waiter = that.__writeWaiters.shift();
                that.__waitingWrite--;
            }

        }

    } else if (that.__priority === Priority.WRITE) {

        // Priority for waiting writers.
        // If there are any writers waiting, don't let readers lock
        if (that.__writeWaiters.length) {

            // Writers require exclusive lock
            if (that.__readLocks === 0 && that.__hasWriteLock === false) {
                waiter = that.__writeWaiters.shift();
                that.__waitingWrite--;
            }

        } else if (that.__readWaiters.length) {

            // If a lock was released, it has been either an exclusive write-lock, or a non-exclusive read-lock.
            // In either case, we can add another read-lock because we know that there's no write lock now.
            waiter = that.__readWaiters.shift();
            that.__waitingRead--;
        }

    } else {

        var nextWaiter = that.__waiters[0];
        if (nextWaiter) {
            if (nextWaiter.w) {

                // Writers require exclusive lock
                if (that.__readLocks === 0 && that.__hasWriteLock === false) {
                    waiter = that.__waiters.shift();
                    that.__waitingWrite--;
                }

            } else {

                // Readers require that no writers are currently locking
                if (that.__hasWriteLock === false) {
                    waiter = that.__waiters.shift();
                    that.__waitingRead--;
                }

            }
        }

    }

    if (waiter) {

        if (waiter.w) {
            that.__hasWriteLock = true;
        } else {
            that.__readLocks++;
        }

        // We have a lock waiting, tell it it's ready
        if (waiter.to) {
            clearTimeout(waiter.to);
        }

        if (waiter.cb) {
            setTimeout(function () {
                // Call with a global `this` and no error
                waiter.cb.call();
            }, 0);
        }

        // Now if something was pulled out of the waiters list,
        // It could have been a read lock when we have more read locks pending.
        that._acquireWaitingLock();
    }

};

/**
 * Acquires a read lock.
 * @public
 * @expose
 * If the lock could not be acquired after the timeout specified then it will fail and return 'timeout' in the callback error.
 * Argument combinations could be (timeout, callback), (timeout), or (callback) :
 * -> (timeout) Timeout after which the lock fails. A negative value will wait indefinitely, zero will fail immediately. (Defaults to -1 - indefinite)
 * -> (callback) The callback to call when the lock is acquired or failed
 * @returns {boolean} Was the lock acquired. If there's a timeout, it might return false and later call the callback with a success.
 */
MemoryLock.prototype.readLock = function () {

    var that = this;

    var timeout = arguments[0];
    var callback = arguments[1];
    if (typeof timeout === 'function') {
        callback = timeout;
        timeout = -1;
    }

    var allowLock = false;

    // Read locks require a read-only mode, no write locks at the same time
    if (that.__hasWriteLock === false) {

        // If no write-locks are waiting,
        // or if there are but the priority is on READ
        if (that.__waitingWrite === 0 ||
            that.__priority === Priority.READ) {
            allowLock = true;
        }
    }

    if (allowLock) {
        that.__readLocks++;
        if (callback) {
            callback();
        }
        return true;
    } else {
        that._handleNoLock(timeout, callback, false);
        return false;
    }
};

/**
 * Acquires a write lock.
 * If the lock could not be acquired after the timeout specified then it will fail and return 'timeout' in the callback error.
 * Argument combinations could be (timeout, callback), (timeout), or (callback) :
 * -> (timeout) Timeout after which the lock fails. A negative value will wait indefinitely, zero will fail immediately. (Defaults to -1 - indefinite)
 * -> (callback) The callback to call when the lock is acquired or failed
 * @public
 * @expose
 * @returns {boolean} Was the lock acquired. If there's a timeout, it might return false and later call the callback with a success.
 */
MemoryLock.prototype.writeLock = function () {

    var that = this;

    var timeout = arguments[0];
    var callback = arguments[1];
    if (typeof timeout === 'function') {
        callback = timeout;
        timeout = -1;
    }

    // Write locks require exclusive access, regardless of priority
    var allowLock = that.__hasWriteLock === false && that.__readLocks === 0;

    if (allowLock) {
        that.__hasWriteLock = true;
        if (callback) {
            callback();
        }
        return true;
    } else {
        that._handleNoLock(timeout, callback, true);
        return false;
    }
};

/**
 * Releases a read lock
 * @public
 * @expose
 * @returns {boolean} Was it unlocked successfully
 */
MemoryLock.prototype.readUnlock = function () {
    if (this.__readLocks === 0) {
        console.log('Fatal error: readUnlock() called when there are no read locks.');
        return false;
    } else {
        this.__readLocks--;

        this._acquireWaitingLock();

        return true;
    }
};

/**
 * Releases a write lock
 * @public
 * @expose
 * @returns {boolean} Was it unlocked successfully
 */
MemoryLock.prototype.writeUnlock = function () {
    if (this.__hasWriteLock === false) {
        console.log('Fatal error: writeUnlock() called when there is no write lock.');
        return false;
    } else {
        this.__hasWriteLock = false;

        this._acquireWaitingLock();

        return true;
    }
};

/**
 * Tries to upgrade a single read-lock to a write-lock.
 *
 *   If there is more than one read lock, or no read locks at all,
 *   or if there is a write lock already - then it will fail immediately and return false.
 *   Otherwise it will succeed immediately and return true.
 *
 * @public
 * @expose
 * @returns {boolean} Did the upgrade succeed?
 */
MemoryLock.prototype.upgradeToWriteLock = function () {
    if (this.__hasWriteLock === false && this.__readLocks === 1) {
        this.__readLocks = 0;
        this.__hasWriteLock = true;
        return true;
    }
    return false;
};

/**
 * Tries to downgrade a write-lock to a read-lock.
 *
 *   If there is no write lock it will fail immediately and return false.
 *   Otherwise it will succeed immediately and return true.
 *
 * @public
 * @expose
 * @returns {boolean} Did the downgrade succeed?
 */
MemoryLock.prototype.downgradeToReadLock = function () {
    if (this.__hasWriteLock === true) {
        this.__readLocks = 1;
        this.__hasWriteLock = false;

        // Look for more read locks...
        this._acquireWaitingLock();

        return true;
    }
    return false;
};

/**
 * Gets or sets the priority for the locker
 *
 * @name MemoryLock.prototype#priority
 * @type Priority
 * @default Priority.UNSPECIFIED
 * @public
 */

/**
 * Retrieves the count of the read locks on the object.
 *
 * @name MemoryLock.prototype#currentReadLocks
 * @type Number
 * @public
 */

/**
 * Returns true if the object is write-locked
 *
 * @name MemoryLock.prototype#hasWriteLock
 * @type Boolean
 * @public
 */

/**
 * Retrieves the count of the pending read locks on the object.
 *
 * @name MemoryLock.prototype#pendingReadLocks
 * @type Number
 * @public
 */

/**
 * Retrieves the count of the pending write locks on the object.
 *
 * @name MemoryLock.prototype#pendingWriteLocks
 * @type Number
 * @public
 */

Object.defineProperties(MemoryLock.prototype, {

    'priority': {
        get: function () { return this.__priority; },
        set: function (newValue) {
            newValue = getValidPriority(newValue);

            var newHasSplitLists = newValue == Priority.READ || newValue == Priority.WRITE;
            if (newHasSplitLists !== this.__hasSplitLists) {
                this.__hasSplitLists = newHasSplitLists;
                if (this.__waiters) {
                    this.__readWaiters = [];
                    this.__writeWaiters = [];
                    for (var i = 0, len = this.__waiters.length; i < len; i++) {
                        var waiter = this.__waiters[i];
                        if (waiter.w) {
                            this.__readWaiters.push(waiter);
                        } else {
                            this.__writeWaiters.push(waiter);
                        }
                    }
                    this.__waiters = null;
                } else {
                    this.__waiters = this.__readWaiters.concat(this.__writeWaiters);
                    this.__waiters.sort(function (a, b) {
                        if (a.t < b.t) return -1;
                        if (a.t > b.t) return 1;
                        return 0;
                    });
                    this.__readWaiters = this.__writeWaiters = null;
                }
            }
        }
    },

    'currentReadLocks': {
        get: function () { return this.__readLocks; }
    },

    'hasWriteLock': {
        get: function () { return this.__hasWriteLock; }
    },

    'pendingReadLocks': {
        get: function () { return this.__waitingRead; }
    },

    'pendingWriteLocks': {
        get: function () { return this.__waitingWrite; }
    }
});

/**
 *
 * @type {Object.<string, MemoryLock>}
 */
var locks = {};

/**
 * @expose
 * @public
 * @param {Object?} options
 * @param {String?} options.name The unique name for the lock
 * @param {Priority?} options.priority Priority policy, enum available in MemoryLock.Priority (UNSPECIFIED, READ, WRITE)
 * @returns {MemoryLock} The locker object
 */
module.exports = function (options) {
    if (options && options.name) {
        return locks[options.name] || (locks[options.name] = new MemoryLock(options));
    } else {
        return new MemoryLock(options);
    }
};

/** @expose */
module.exports.Priority = Priority;

/** @expose */
MemoryLock.Priority = Priority;
<commit_msg>fix waiter removal from waiters array<commit_after>"use strict";

/** @enum {string}
 */
var Priority = {
    /**
     * A lock with an UNSPECIFIED priority will try to acquire the locks in the order that they arrived
     */
    UNSPECIFIED: 'unspecified',

    /**
     * A lock with a READ priority will grant priority to read-locks,
     * meaning that if there's a pending write lock, new read locks will still be acquired.
     */
    READ: 'read',

    /**
     * A lock with a WRITE priority will grant priority to read-locks,
     * meaning that if there's a pending write lock, new read locks will not be acquired.
     */
    WRITE: 'write'
};

/**
 * Normalizes a priority value
 * @param {Priority} priority
 * @returns {Priority}
 */
var getValidPriority = function (priority) {

    if (priority !== Priority.READ &&
        priority !== Priority.WRITE &&
        priority !== Priority.UNSPECIFIED) {
        return Priority.UNSPECIFIED;
    }

    return priority;

};

/**
 * @class
 */
var MemoryLock = function ctor () {
    if (!(this instanceof ctor)) {
        var instance = Object.create(MemoryLock.prototype);
        (typeof this.init === 'function') && this.init.apply(this, args);
        return instance;
    }
    (typeof this.init === 'function') && this.init.apply(this, arguments);
};

/**
 * @public
 * @param {Object?} options
 * @param {String?} options.name The unique name for the lock
 * @param {Priority?} options.priority Priority policy, enum available in MemoryLock.Priority (UNSPECIFIED, READ, WRITE)
 */
MemoryLock.prototype.init = function (options) {

    var that = this;
    options = options || {};

    that.__name = options.name || null;
    var priority = that.__priority = getValidPriority(options.priority);

    that.__hasSplitLists = priority == Priority.READ || priority == Priority.WRITE;

    if (that.__hasSplitLists) {
        /** @member {Array?} */
        that.__readWaiters = [];

        /** @member {Array?} */
        that.__writeWaiters = [];
    } else {
        /** @member {Array?} */
        that.__waiters = [];
    }

    /** @member {Number} */
    that.__readLocks = 0;

    /** @member {Boolean} */
    that.__hasWriteLock = false;

    /** @member {Number} */
    that.__waitingWrite = 0;

    /** @member {Number} */
    that.__waitingRead = 0;

};

/**
 * Returns the relevant waiters list
 * @private
 * @param {Boolean} isWrite is this for a write lock or a read lock?
 * @returns {*}
 */
MemoryLock.prototype._getWaiterList = function (isWrite) {
    return this.__hasSplitLists ? (isWrite ? this.__writeWaiters : this.__readWaiters) : this.__waiters;
};

/**
 * This handles the "no" situation, creating the timeout if necessary or calling the callback
 * @private
 * @param timeout
 * @param callback
 * @param isWrite
 */
MemoryLock.prototype._handleNoLock = function (timeout, callback, isWrite) {

    var that = this;

    if (timeout !== 0) {
        var waiter = { cb: callback, w: isWrite, t: Date.now() };

        // Add this lock to the waiting list
        that._getWaiterList(isWrite).push(waiter);

        if (isWrite) {
            that.__waitingWrite++;
        } else {
            that.__waitingRead++;
        }

        // Create the timeout for this waiting lock

        if (timeout > 0) {
            waiter.to = setTimeout(function() {

                // Remove it from the waiter list
                var waiters = that._getWaiterList(isWrite);
                var idx = waiters.indexOf(waiter);
                if (idx !== -1) waiters.splice(idx, 1);

                if (isWrite) {
                    that.__waitingWrite--;
                } else {
                    that.__waitingRead--;
                }

                // We cannot allow an exception here. We'll postpone them until after the lock's logic is done.
                var throwable = null;

                if (waiter.cb) {
                    try {
                        // Call with a global `this` and a 'timeout' error
                        waiter.cb.call(undefined, 'timeout');
                    } catch (e) {
                        throwable = e;
                    }
                }

                // It could be a write lock that has failed, and now read locks can be acquired...
                that._acquireWaitingLock();

                // Re-throw any error that came from the callback
                if (throwable) {
                    throw throwable;
                }

            }, timeout);
        }

    } else {

        if (callback) {
            // Call with a global `this` and a 'timeout' error
            callback.call('timeout');
        }

    }

};

/**
 * Looks for the next lock to acquire if any are waiting
 * @private
 */
MemoryLock.prototype._acquireWaitingLock = function () {

    var that = this;
    var waiter;

    if (that.__priority === Priority.READ) {

        // Priority for waiting readers
        if (that.__readWaiters.length) {

            // We can read-lock only if there are no write locks
            if (that.__hasWriteLock === false) {
                waiter = that.__readWaiters.shift();
                that.__waitingRead--;
            }

        } else if (that.__writeWaiters.length) {

            // Writers require exclusive lock
            if (that.__readLocks === 0 && that.__hasWriteLock === false) {
                waiter = that.__writeWaiters.shift();
                that.__waitingWrite--;
            }

        }

    } else if (that.__priority === Priority.WRITE) {

        // Priority for waiting writers.
        // If there are any writers waiting, don't let readers lock
        if (that.__writeWaiters.length) {

            // Writers require exclusive lock
            if (that.__readLocks === 0 && that.__hasWriteLock === false) {
                waiter = that.__writeWaiters.shift();
                that.__waitingWrite--;
            }

        } else if (that.__readWaiters.length) {

            // If a lock was released, it has been either an exclusive write-lock, or a non-exclusive read-lock.
            // In either case, we can add another read-lock because we know that there's no write lock now.
            waiter = that.__readWaiters.shift();
            that.__waitingRead--;
        }

    } else {

        var nextWaiter = that.__waiters[0];
        if (nextWaiter) {
            if (nextWaiter.w) {

                // Writers require exclusive lock
                if (that.__readLocks === 0 && that.__hasWriteLock === false) {
                    waiter = that.__waiters.shift();
                    that.__waitingWrite--;
                }

            } else {

                // Readers require that no writers are currently locking
                if (that.__hasWriteLock === false) {
                    waiter = that.__waiters.shift();
                    that.__waitingRead--;
                }

            }
        }

    }

    if (waiter) {

        if (waiter.w) {
            that.__hasWriteLock = true;
        } else {
            that.__readLocks++;
        }

        // We have a lock waiting, tell it it's ready
        if (waiter.to) {
            clearTimeout(waiter.to);
        }

        if (waiter.cb) {
            setTimeout(function () {
                // Call with a global `this` and no error
                waiter.cb.call();
            }, 0);
        }

        // Now if something was pulled out of the waiters list,
        // It could have been a read lock when we have more read locks pending.
        that._acquireWaitingLock();
    }

};

/**
 * Acquires a read lock.
 * @public
 * @expose
 * If the lock could not be acquired after the timeout specified then it will fail and return 'timeout' in the callback error.
 * Argument combinations could be (timeout, callback), (timeout), or (callback) :
 * -> (timeout) Timeout after which the lock fails. A negative value will wait indefinitely, zero will fail immediately. (Defaults to -1 - indefinite)
 * -> (callback) The callback to call when the lock is acquired or failed
 * @returns {boolean} Was the lock acquired. If there's a timeout, it might return false and later call the callback with a success.
 */
MemoryLock.prototype.readLock = function () {

    var that = this;

    var timeout = arguments[0];
    var callback = arguments[1];
    if (typeof timeout === 'function') {
        callback = timeout;
        timeout = -1;
    }

    var allowLock = false;

    // Read locks require a read-only mode, no write locks at the same time
    if (that.__hasWriteLock === false) {

        // If no write-locks are waiting,
        // or if there are but the priority is on READ
        if (that.__waitingWrite === 0 ||
            that.__priority === Priority.READ) {
            allowLock = true;
        }
    }

    if (allowLock) {
        that.__readLocks++;
        if (callback) {
            callback();
        }
        return true;
    } else {
        that._handleNoLock(timeout, callback, false);
        return false;
    }
};

/**
 * Acquires a write lock.
 * If the lock could not be acquired after the timeout specified then it will fail and return 'timeout' in the callback error.
 * Argument combinations could be (timeout, callback), (timeout), or (callback) :
 * -> (timeout) Timeout after which the lock fails. A negative value will wait indefinitely, zero will fail immediately. (Defaults to -1 - indefinite)
 * -> (callback) The callback to call when the lock is acquired or failed
 * @public
 * @expose
 * @returns {boolean} Was the lock acquired. If there's a timeout, it might return false and later call the callback with a success.
 */
MemoryLock.prototype.writeLock = function () {

    var that = this;

    var timeout = arguments[0];
    var callback = arguments[1];
    if (typeof timeout === 'function') {
        callback = timeout;
        timeout = -1;
    }

    // Write locks require exclusive access, regardless of priority
    var allowLock = that.__hasWriteLock === false && that.__readLocks === 0;

    if (allowLock) {
        that.__hasWriteLock = true;
        if (callback) {
            callback();
        }
        return true;
    } else {
        that._handleNoLock(timeout, callback, true);
        return false;
    }
};

/**
 * Releases a read lock
 * @public
 * @expose
 * @returns {boolean} Was it unlocked successfully
 */
MemoryLock.prototype.readUnlock = function () {
    if (this.__readLocks === 0) {
        console.log('Fatal error: readUnlock() called when there are no read locks.');
        return false;
    } else {
        this.__readLocks--;

        this._acquireWaitingLock();

        return true;
    }
};

/**
 * Releases a write lock
 * @public
 * @expose
 * @returns {boolean} Was it unlocked successfully
 */
MemoryLock.prototype.writeUnlock = function () {
    if (this.__hasWriteLock === false) {
        console.log('Fatal error: writeUnlock() called when there is no write lock.');
        return false;
    } else {
        this.__hasWriteLock = false;

        this._acquireWaitingLock();

        return true;
    }
};

/**
 * Tries to upgrade a single read-lock to a write-lock.
 *
 *   If there is more than one read lock, or no read locks at all,
 *   or if there is a write lock already - then it will fail immediately and return false.
 *   Otherwise it will succeed immediately and return true.
 *
 * @public
 * @expose
 * @returns {boolean} Did the upgrade succeed?
 */
MemoryLock.prototype.upgradeToWriteLock = function () {
    if (this.__hasWriteLock === false && this.__readLocks === 1) {
        this.__readLocks = 0;
        this.__hasWriteLock = true;
        return true;
    }
    return false;
};

/**
 * Tries to downgrade a write-lock to a read-lock.
 *
 *   If there is no write lock it will fail immediately and return false.
 *   Otherwise it will succeed immediately and return true.
 *
 * @public
 * @expose
 * @returns {boolean} Did the downgrade succeed?
 */
MemoryLock.prototype.downgradeToReadLock = function () {
    if (this.__hasWriteLock === true) {
        this.__readLocks = 1;
        this.__hasWriteLock = false;

        // Look for more read locks...
        this._acquireWaitingLock();

        return true;
    }
    return false;
};

/**
 * Gets or sets the priority for the locker
 *
 * @name MemoryLock.prototype#priority
 * @type Priority
 * @default Priority.UNSPECIFIED
 * @public
 */

/**
 * Retrieves the count of the read locks on the object.
 *
 * @name MemoryLock.prototype#currentReadLocks
 * @type Number
 * @public
 */

/**
 * Returns true if the object is write-locked
 *
 * @name MemoryLock.prototype#hasWriteLock
 * @type Boolean
 * @public
 */

/**
 * Retrieves the count of the pending read locks on the object.
 *
 * @name MemoryLock.prototype#pendingReadLocks
 * @type Number
 * @public
 */

/**
 * Retrieves the count of the pending write locks on the object.
 *
 * @name MemoryLock.prototype#pendingWriteLocks
 * @type Number
 * @public
 */

Object.defineProperties(MemoryLock.prototype, {

    'priority': {
        get: function () { return this.__priority; },
        set: function (newValue) {
            newValue = getValidPriority(newValue);

            var newHasSplitLists = newValue == Priority.READ || newValue == Priority.WRITE;
            if (newHasSplitLists !== this.__hasSplitLists) {
                this.__hasSplitLists = newHasSplitLists;
                if (this.__waiters) {
                    this.__readWaiters = [];
                    this.__writeWaiters = [];
                    for (var i = 0, len = this.__waiters.length; i < len; i++) {
                        var waiter = this.__waiters[i];
                        if (waiter.w) {
                            this.__readWaiters.push(waiter);
                        } else {
                            this.__writeWaiters.push(waiter);
                        }
                    }
                    this.__waiters = null;
                } else {
                    this.__waiters = this.__readWaiters.concat(this.__writeWaiters);
                    this.__waiters.sort(function (a, b) {
                        if (a.t < b.t) return -1;
                        if (a.t > b.t) return 1;
                        return 0;
                    });
                    this.__readWaiters = this.__writeWaiters = null;
                }
            }
        }
    },

    'currentReadLocks': {
        get: function () { return this.__readLocks; }
    },

    'hasWriteLock': {
        get: function () { return this.__hasWriteLock; }
    },

    'pendingReadLocks': {
        get: function () { return this.__waitingRead; }
    },

    'pendingWriteLocks': {
        get: function () { return this.__waitingWrite; }
    }
});

/**
 *
 * @type {Object.<string, MemoryLock>}
 */
var locks = {};

/**
 * @expose
 * @public
 * @param {Object?} options
 * @param {String?} options.name The unique name for the lock
 * @param {Priority?} options.priority Priority policy, enum available in MemoryLock.Priority (UNSPECIFIED, READ, WRITE)
 * @returns {MemoryLock} The locker object
 */
module.exports = function (options) {
    if (options && options.name) {
        return locks[options.name] || (locks[options.name] = new MemoryLock(options));
    } else {
        return new MemoryLock(options);
    }
};

/** @expose */
module.exports.Priority = Priority;

/** @expose */
MemoryLock.Priority = Priority;
<|endoftext|>


<commit_before>const _ = require('lodash');
const cheerio = require('cheerio');
const express = require('express');
const bodyParser = require('body-parser');
const Promise = require('bluebird');
const Trello = require('node-trello');
const rp = require('request-promise');

require('dotenv').config();
Promise.promisifyAll(Trello.prototype);

const conferences = require('./conferences.json');

const getConference = (name) =>
  (conferences[name] != null) ? _.extend(conferences[name], { name }) : null;

const labelColors = ['green', 'yellow', 'orange', 'red', 'purple', 'blue',
 'sky', 'lime', 'pink', 'black'];

const app = express();
app.set('view engine', 'pug');
app.use(bodyParser.urlencoded({ extended: true }));

app.use(express.static('public'));

app.get('/:conference?', function(req, res) {
  const conference = getConference(req.query.conference || req.params.conference);

  if (conference == null) {
    if (req.params.conference != null) {
      res.redirect('/');
    } else {
      res.render('select_conference', {
        conferences
      });
    }
  } else {
    res.render('index', {
      appKey: process.env.TRELLO_KEY,
      conference: conference,
    });
  }
})

app.post('/', function(req, res){
  const { board_idOrg, token } = req.body;

  const board_name = req.body.board_name.trim()

  const conference = getConference(req.body.conference);

  if (conference == null) {
    res.redirect(303, '/');
    return;
  }

  conference.name = req.body.conference;

  const fetcher = require(`./conferences/${conference.name}.js`);

  try {
    if (!board_name) {
      throw new Error('You need to specify a board name');
    }
    if (!token) {
      throw new Error('You need to pass a Trello token');
    }
  }
  catch (err) {
    res.status(400).send(err.message);
    return;
  }

  const trello = new Trello(process.env.TRELLO_KEY, token);

  const write = (s, newLine = true) => res.write(s + (newLine ? "

" : ''));
  const willWrite = (s, newLine = true) => () => write(s, newLine)

  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf8' });

  write('<title>Creating conference board</title>\
    <link rel="stylesheet" href="/style.css" />\
    \

');

  write(`Downloading ${conference.scheduleUrl}... `, false)

  fetcher(conference.scheduleUrl)
  .then(({ days, labels }) => {
    const labelIds = {};

    write("done")

    write(`Creating "${board_name}" board... `, false)

    return trello.postAsync('/1/boards', {
      name: board_name,
      idOrganization: board_idOrg,
      defaultLabels: false,
      defaultLists: false,
    })
    .tap(board => write(`done — <a href="${board.url}" target="_blank">watch it grow!</a>`))
    // add 'how to use' list
    .then(board =>
      trello.postAsync('/1/lists', {
        idBoard: board.id,
        name: 'How to use this board',
      })
      .tap(willWrite("Setting up the board... "))
      .tap(willWrite("  - instruction cards", false))
      .then(list =>
        Promise.each([
            '** BEFORE THE CONFERENCE **',
            'Add all your team mates that are going the conference to this board.',
            'Choose which session you\'re going to in each slot by adding yourself to the card.',
            '** DURING THE CONFERENCE **',
            'Use this board on your laptop, iPhone, iPad or Android device with Trello\'s mobile apps.',
            'You can write notes as comments on the session\'s card, and attach any photos or links to elsewhere to the card too.',
            'Press `q` to see your personal schedule.',
            'Press `f` to filter by a label or some text.',
            '** AFTER THE CONFERENCE **',
            'You have a central resource with all the details of the conference, including your whole team\'s notes, photos and links.',
            'When the conference videos are released, link to each one on its card.',
            'Share the board with everyone else at your company so they can learn too, including your notes on how this can apply to your situation!',
          ],
          name =>
            trello.postAsync('/1/cards', { name, idList: list.id })
            .tap(willWrite('.', false))
        )
        .tap(willWrite(' ✓'))
        .then(() => board)
      )
    )
    .tap(willWrite(`  - ${labels.length} labels`, false))
    // create labels
    .tap(board =>
      Promise.each(labels, (name, index) =>
        trello.postAsync(`/1/boards/${board.id}/labels`, {
          name,
          color: labelColors[index % labelColors.length+1]
        })
        .then(label =>
          labelIds[name] = label.id
        )
        .tap(willWrite('.', false))
      )
    )
    .tap(willWrite(' ✓'))
    .tap(willWrite('Creating lists and cards for each day... '))
    // add each day's slots and sessions
    .tap(board =>
      Promise.each(days, day =>
        trello.postAsync('/1/lists', {
          idBoard: board.id,
          name: day.title,
          pos: 'bottom',
        })
        .tap(dayList => trello.postAsync('/1/cards', { name: 'Goals for this day', idList: dayList.id }))
        .tap(dayList => trello.postAsync('/1/cards', { name: 'Key takeaways', idList: dayList.id }))
        .tap(willWrite(`  ${day.title}`))
        .then(() =>
          Promise.each(day.slots, slot =>
            trello.postAsync('/1/lists', {
              idBoard: board.id,
              name: slot.time,
              pos: 'bottom',
            })
            .tap(willWrite(`    ${slot.time}`, false))
            .then(list =>
              Promise.each(slot.sessions, session =>
                trello.postAsync('/1/cards', {
                  name: `${session.title} — ${session.speakers}`,
                  desc: session.blurb,
                  idLabels: session.labels.map(label => labelIds[label]).join(','),
                  idList: list.id
                })
                .then(card =>
                  Promise.each(session.avatars.reverse(), avatarUrl =>
                    trello.postAsync(`/1/cards/${card.id}/attachments`, { url: avatarUrl })
                  ) // Promise.each
                ) // then card
                .tap(willWrite('.', false))
              ) // each session
            ) // then list
            .tap(willWrite(' ✓'))
          ) // each slot
        ) // then dayList
      ) // each day
    )
    .then(board => {
      res.write(`\n\nAll Done! <a href="${board.url}">${board.url}</a></p></article></body></html>`)
      res.end();
    })
  })
  .catch(err => {
    console.error(err);
    res.write(`</p><p style="color: red">Error: ${err.message}</p><p>You might want to close the board in Trello and refresh this page to try again.</p></article></body></html>`);
    res.end()
    throw err;
  })
});

const port = process.env.PORT || 3002;
app.listen(port);

console.log(`listening on ${port}`)
<commit_msg>Rate limit Trello API requests<commit_after>const _ = require('lodash');
const cheerio = require('cheerio');
const express = require('express');
const bodyParser = require('body-parser');
const Promise = require('bluebird');
const Trello = require('node-trello');
const rp = require('request-promise');

require('dotenv').config();
Promise.promisifyAll(Trello.prototype);

const TRELLO_MAX_RATE = 300; // max one request every x milliseconds
const conferences = require('./conferences.json');

const getConference = (name) =>
  (conferences[name] != null) ? _.extend(conferences[name], { name }) : null;

const labelColors = ['green', 'yellow', 'orange', 'red', 'purple', 'blue',
 'sky', 'lime', 'pink', 'black'];

const app = express();
app.set('view engine', 'pug');
app.use(bodyParser.urlencoded({ extended: true }));

app.use(express.static('public'));

app.get('/:conference?', function(req, res) {
  const conference = getConference(req.query.conference || req.params.conference);

  if (conference == null) {
    if (req.params.conference != null) {
      res.redirect('/');
    } else {
      res.render('select_conference', {
        conferences
      });
    }
  } else {
    res.render('index', {
      appKey: process.env.TRELLO_KEY,
      conference: conference,
    });
  }
})

app.post('/', function(req, res){
  const { board_idOrg, token } = req.body;

  const board_name = req.body.board_name.trim()

  const conference = getConference(req.body.conference);

  if (conference == null) {
    res.redirect(303, '/');
    return;
  }

  conference.name = req.body.conference;

  const fetcher = require(`./conferences/${conference.name}.js`);

  try {
    if (!board_name) {
      throw new Error('You need to specify a board name');
    }
    if (!token) {
      throw new Error('You need to pass a Trello token');
    }
  }
  catch (err) {
    res.status(400).send(err.message);
    return;
  }

  const trello = new Trello(process.env.TRELLO_KEY, token);

  let lastTrelloRequest = null
  // rate limited Trello API requests
  const trelloPost = (path, params) => {
    const now = Date.now();
    const delta = now - lastTrelloRequest;
    lastTrelloRequest = now;
    const doThePost = () => trello.postAsync(path, params)

    if (delta < TRELLO_MAX_RATE) {
      return Promise.delay(TRELLO_MAX_RATE - delta).then(doThePost)
    } else {
      return doThePost();
    }
  }

  const write = (s, newLine = true) => res.write(s + (newLine ? "

" : ''));
  const willWrite = (s, newLine = true) => () => write(s, newLine)

  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf8' });

  write('<title>Creating conference board</title>\
    <link rel="stylesheet" href="/style.css" />\
    \

');

  write(`Downloading ${conference.scheduleUrl}... `, false)

  fetcher(conference.scheduleUrl)
  .then(({ days, labels }) => {
    const labelIds = {};

    write("done")

    write(`Creating "${board_name}" board... `, false)

    return trelloPost('/1/boards', {
      name: board_name,
      idOrganization: board_idOrg,
      defaultLabels: false,
      defaultLists: false,
    })
    .tap(board => write(`done — <a href="${board.url}" target="_blank">watch it grow!</a>`))
    // add 'how to use' list
    .then(board =>
      trelloPost('/1/lists', {
        idBoard: board.id,
        name: 'How to use this board',
      })
      .tap(willWrite("Setting up the board... "))
      .tap(willWrite("  - instruction cards", false))
      .then(list =>
        Promise.each([
            '** BEFORE THE CONFERENCE **',
            'Add all your team mates that are going the conference to this board.',
            'Choose which session you\'re going to in each slot by adding yourself to the card.',
            '** DURING THE CONFERENCE **',
            'Use this board on your laptop, iPhone, iPad or Android device with Trello\'s mobile apps.',
            'You can write notes as comments on the session\'s card, and attach any photos or links to elsewhere to the card too.',
            'Press `q` to see your personal schedule.',
            'Press `f` to filter by a label or some text.',
            '** AFTER THE CONFERENCE **',
            'You have a central resource with all the details of the conference, including your whole team\'s notes, photos and links.',
            'When the conference videos are released, link to each one on its card.',
            'Share the board with everyone else at your company so they can learn too, including your notes on how this can apply to your situation!',
          ],
          name =>
            trelloPost('/1/cards', { name, idList: list.id })
            .tap(willWrite('.', false))
        )
        .tap(willWrite(' ✓'))
        .then(() => board)
      )
    )
    .tap(willWrite(`  - ${labels.length} labels`, false))
    // create labels
    .tap(board =>
      Promise.each(labels, (name, index) =>
        trelloPost(`/1/boards/${board.id}/labels`, {
          name,
          color: labelColors[index % labelColors.length+1]
        })
        .then(label =>
          labelIds[name] = label.id
        )
        .tap(willWrite('.', false))
      )
    )
    .tap(willWrite(' ✓'))
    .tap(willWrite('Creating lists and cards for each day... '))
    // add each day's slots and sessions
    .tap(board =>
      Promise.each(days, day =>
        trelloPost('/1/lists', {
          idBoard: board.id,
          name: day.title,
          pos: 'bottom',
        })
        .tap(dayList => trelloPost('/1/cards', { name: 'Goals for this day', idList: dayList.id }))
        .tap(dayList => trelloPost('/1/cards', { name: 'Key takeaways', idList: dayList.id }))
        .tap(willWrite(`  ${day.title}`))
        .then(() =>
          Promise.each(day.slots, slot =>
            trelloPost('/1/lists', {
              idBoard: board.id,
              name: slot.time,
              pos: 'bottom',
            })
            .tap(willWrite(`    ${slot.time}`, false))
            .then(list =>
              Promise.each(slot.sessions, session =>
                trelloPost('/1/cards', {
                  name: `${session.title} — ${session.speakers}`,
                  desc: session.blurb,
                  idLabels: session.labels.map(label => labelIds[label]).join(','),
                  idList: list.id
                })
                .then(card =>
                  Promise.each(session.avatars.reverse(), avatarUrl =>
                    trelloPost(`/1/cards/${card.id}/attachments`, { url: avatarUrl })
                  ) // Promise.each
                ) // then card
                .tap(willWrite('.', false))
              ) // each session
            ) // then list
            .tap(willWrite(' ✓'))
          ) // each slot
        ) // then dayList
      ) // each day
    )
    .then(board => {
      res.write(`\n\nAll Done! <a href="${board.url}">${board.url}</a></p></article></body></html>`)
      res.end();
    })
  })
  .catch(err => {
    console.error(err);
    res.write(`</p><p style="color: red">Error: ${err.message}</p><p>You might want to close the board in Trello and refresh this page to try again.</p></article></body></html>`);
    res.end()
    throw err;
  })
});

const port = process.env.PORT || 3002;
app.listen(port);

console.log(`listening on ${port}`)
<|endoftext|>


<commit_before>// Dependencies
var Mongo = require("mongodb")
  , MongoClient = Mongo.MongoClient
  , EventEmitter = require("events").EventEmitter
  , Fs = require("fs")
  , Path = require("path")
  ;

/**
 * JsonDb
 * Creates a new instance of JsonDb.
 *
 * @name JsonDb
 * @function
 * @param {Object} options The options object of the constructor.
 * @return {EventEmitter} The instance of JsonDb object
 */
var JsonDb = module.exports = function (options) {

    // Force options to be an object and process data
    options = Object(options);
    options.collections = options.collections || [];
    options.ignoreSyncFor = options.ignoreSyncFor || [];
    options.ignoreCallbackFor = options.ignoreCallbackFor || ["find", "findOne"];

    // Initialize self
    var self = new EventEmitter();
    self._options = options;
    self._instance = this;
    self._cache = {};

    /**
     * addInCache
     * Cache database and collection
     *
     * @name addInCache
     * @function
     * @param {String} uri The Mongo database URI string
     * @param {Object} dbObj Database object
     * @param {String} colName Collection name (optional)
     * @param {Object} colObj Collection object (optional)
     */
    self.addInCache = function (uri, dbObj, colName, colObj) {

        var uriCache = self._cache[uri] = self._cache[uri] || {};
        uriCache.db = dbObj;

        if (!colName || !colObj) { return self; }

        uriCache.collections = uriCache.collections || {};
        uriCache.collections[colName] = colObj;
    };

    /**
     * getDatabase
     * Returns (via callback) a database object from cache or fetched via Mongo functions.
     *
     * @name getDatabase
     * @function
     * @param {String} uri The Mongo database URI string
     * @param {Function} callback The callback function
     */
    self.getDatabase = function (uri, callback) {

        var cached = self._cache[uri];
        if (cached) { return callback(null, cached); }

        MongoClient.connect(uri, function(err, db) {
            if (err) { return callback(err); }
            callback(null, self._cache[uri] = db);
        });
    };

    /**
     * getCollection
     * Returns (via callback) the collection object from cache
     *
     * @name getCollection
     * @function
     * @param {String} dbUri The Mongo database URI string
     * @param {String} collection Collection name
     * @param {Function} callback The callback function
     */
    self.getCollection = function (dbUri, collection, callback) {

        var cached = Object(Object(self._cache[dbUri]).collections)[collection];
        if (cached) { return callback(null, cached); }

        self.getDatabase(dbUri, function (err, db) {
            if (err) { return callback(err); }
            callback(null, self._cache[dbUri].collections[collection] = db.collection(collection), db);
        });
    };

    /**
     * initCollection
     * Inits the collection and returns the collection instance.
     *
     * @name initCollection
     * @function
     * @param {Object} options The options for initing the collection
     * @param {Function} callback The callback function
     * @return {EventEmitter} The instance of collection object.
     */
    self.initCollection = function (options, callback) {

        var uri = options.uri
          , inputFile = options.inputFile
          , outputFile = options.outputFile
          , collection = options.collection
          , collectionInstance = new EventEmitter()
          ;

        self.getCollection(uri, collection, function (err, col, db) {
            if (err) { return callback(err); }

            // Attach collection and database fields
            collectionInstance.collection = col;
            collectionInstance.database = db;

            // Attach Mongo functions
            for (var key in col) {
                (function (key) {
                    var keyValue = col[key];

                    // Attach the new method
                    collectionInstance[key] = function () {

                        // Handle arguments
                        var args = Array.prototype.slice.call(arguments, 0);
                        args.sort();

                        // Get provided callback
                        var providedCallback = args[args.length - 1];

                        if (typeof providedCallback === "function") {
                            args.splice(args.length - 1, 1);
                        }

                        if (self._options.ignoreCallbackFor.indexOf(key) === -1) {
                            // Push push the real callback
                            args.push(function (err, data) {

                                var cSelf = this
                                  , cArguments = arguments
                                  , opCallback = function () {
                                        if (typeof providedCallback !== "function") { return; }
                                        providedCallback.apply(this, cArguments);
                                    }
                                  ;

                                // Ignore sync
                                if (self._options.ignoreSyncFor.indexOf(key) !== -1) {
                                    return opCallback.apply(cSelf, cArguments);
                                }

                                if (err) { return opCallback.call(cSelf, err); }

                                // Stringify the documents
                                col.find({}).toArray(function (err, docs) {
                                    if (err) { return opCallback.call(cSelf, err); }
                                    Fs.writeFile(options.outputFile, JSON.stringify(docs, null, 2), function (err) {
                                        if (err) { return opCallback.call(cSelf, err); }
                                        opCallback.apply(cSelf, cArguments);
                                    });
                                });
                            });
                        }

                        return col[key].apply(col, args);
                    }
                })(key);
            }

            if (options.autoInit === true) {
                try {
                    data = require(Path.resolve(options.inputFile))
                } catch (e) { data = null }
                if (data) {
                    collectionInstance.remove({}, {multi: true}, function (err, data) {
                        if (err) { return callback(err); }
                        collectionInstance.insert(data, callback);
                    });
                } else {
                    callback(null, col);
                }
            } else {
                callback(null, col);
            }
        });

        return collectionInstance;
    };

    var callbackData = {err: [], data: []};
    for (var i = 0; i < options.collections.length; ++i) {
        self.initCollection(options.collections[i], function (err, data) {
            if (--i <= 0) {
                if (!callbackData.err.length) {
                    callbackData.err = null;
                }
                return callback(callbackData.err, callbackData.data);
            }
        });
    }

    return self;
};
<commit_msg>Attach options to collectionInstance object<commit_after>// Dependencies
var Mongo = require("mongodb")
  , MongoClient = Mongo.MongoClient
  , EventEmitter = require("events").EventEmitter
  , Fs = require("fs")
  , Path = require("path")
  ;

/**
 * JsonDb
 * Creates a new instance of JsonDb.
 *
 * @name JsonDb
 * @function
 * @param {Object} options The options object of the constructor.
 * @return {EventEmitter} The instance of JsonDb object
 */
var JsonDb = module.exports = function (options) {

    // Force options to be an object and process data
    options = Object(options);
    options.collections = options.collections || [];
    options.ignoreSyncFor = options.ignoreSyncFor || [];
    options.ignoreCallbackFor = options.ignoreCallbackFor || ["find", "findOne"];

    // Initialize self
    var self = new EventEmitter();
    self._options = options;
    self._instance = this;
    self._cache = {};

    /**
     * addInCache
     * Cache database and collection
     *
     * @name addInCache
     * @function
     * @param {String} uri The Mongo database URI string
     * @param {Object} dbObj Database object
     * @param {String} colName Collection name (optional)
     * @param {Object} colObj Collection object (optional)
     */
    self.addInCache = function (uri, dbObj, colName, colObj) {

        var uriCache = self._cache[uri] = self._cache[uri] || {};
        uriCache.db = dbObj;

        if (!colName || !colObj) { return self; }

        uriCache.collections = uriCache.collections || {};
        uriCache.collections[colName] = colObj;
    };

    /**
     * getDatabase
     * Returns (via callback) a database object from cache or fetched via Mongo functions.
     *
     * @name getDatabase
     * @function
     * @param {String} uri The Mongo database URI string
     * @param {Function} callback The callback function
     */
    self.getDatabase = function (uri, callback) {

        var cached = self._cache[uri];
        if (cached) { return callback(null, cached); }

        MongoClient.connect(uri, function(err, db) {
            if (err) { return callback(err); }
            callback(null, self._cache[uri] = db);
        });
    };

    /**
     * getCollection
     * Returns (via callback) the collection object from cache
     *
     * @name getCollection
     * @function
     * @param {String} dbUri The Mongo database URI string
     * @param {String} collection Collection name
     * @param {Function} callback The callback function
     */
    self.getCollection = function (dbUri, collection, callback) {

        var cached = Object(Object(self._cache[dbUri]).collections)[collection];
        if (cached) { return callback(null, cached); }

        self.getDatabase(dbUri, function (err, db) {
            if (err) { return callback(err); }
            callback(null, self._cache[dbUri].collections[collection] = db.collection(collection), db);
        });
    };

    /**
     * initCollection
     * Inits the collection and returns the collection instance.
     *
     * @name initCollection
     * @function
     * @param {Object} options The options for initing the collection
     * @param {Function} callback The callback function
     * @return {EventEmitter} The instance of collection object.
     */
    self.initCollection = function (options, callback) {

        var uri = options.uri
          , inputFile = options.inputFile
          , outputFile = options.outputFile
          , collection = options.collection
          , collectionInstance = new EventEmitter()
          ;

        collectionInstance._options = options;

        self.getCollection(uri, collection, function (err, col, db) {
            if (err) { return callback(err); }

            // Attach collection and database fields
            collectionInstance.collection = col;
            collectionInstance.database = db;

            // Attach Mongo functions
            for (var key in col) {
                (function (key) {
                    var keyValue = col[key];

                    // Attach the new method
                    collectionInstance[key] = function () {

                        // Handle arguments
                        var args = Array.prototype.slice.call(arguments, 0);
                        args.sort();

                        // Get provided callback
                        var providedCallback = args[args.length - 1];

                        if (typeof providedCallback === "function") {
                            args.splice(args.length - 1, 1);
                        }

                        if (self._options.ignoreCallbackFor.indexOf(key) === -1) {
                            // Push push the real callback
                            args.push(function (err, data) {

                                var cSelf = this
                                  , cArguments = arguments
                                  , opCallback = function () {
                                        if (typeof providedCallback !== "function") { return; }
                                        providedCallback.apply(this, cArguments);
                                    }
                                  ;

                                // Ignore sync
                                if (self._options.ignoreSyncFor.indexOf(key) !== -1) {
                                    return opCallback.apply(cSelf, cArguments);
                                }

                                if (err) { return opCallback.call(cSelf, err); }

                                // Stringify the documents
                                col.find({}).toArray(function (err, docs) {
                                    if (err) { return opCallback.call(cSelf, err); }
                                    Fs.writeFile(options.outputFile, JSON.stringify(docs, null, 2), function (err) {
                                        if (err) { return opCallback.call(cSelf, err); }
                                        opCallback.apply(cSelf, cArguments);
                                    });
                                });
                            });
                        }

                        return col[key].apply(col, args);
                    }
                })(key);
            }

            if (options.autoInit === true) {
                try {
                    data = require(Path.resolve(options.inputFile))
                } catch (e) { data = null }
                if (data) {
                    collectionInstance.remove({}, {multi: true}, function (err, data) {
                        if (err) { return callback(err); }
                        collectionInstance.insert(data, callback);
                    });
                } else {
                    callback(null, col);
                }
            } else {
                callback(null, col);
            }
        });

        return collectionInstance;
    };

    var callbackData = {err: [], data: []};
    for (var i = 0; i < options.collections.length; ++i) {
        self.initCollection(options.collections[i], function (err, data) {
            if (--i <= 0) {
                if (!callbackData.err.length) {
                    callbackData.err = null;
                }
                return callback(callbackData.err, callbackData.data);
            }
        });
    }

    return self;
};
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');
var path = require('path');
var http = require('http');
var https = require('https');
var app = module.exports = require('./server/server');

if (require.main === module) {
    app.boot()
    .then(() => {
        let httpOnly = process.env.HTTP;
        if (typeof httpOnly === 'undefined') httpOnly = false;
        let server = null;
        if (httpOnly) {
            server = http.createServer(app);
        } else {
            let options = {
                key: fs.readFileSync(path.join('.', 'private', 'privatekey.pem')),
                cert: fs.readFileSync(path.join('.', 'private', 'certificate.pem'))
            };
            server = https.createServer(options, app);
        }
        server.listen(app.get('port'), () => {
            let baseUrl = `${httpOnly ? 'http' : 'https'}://${app.get('host')}:${app.get('port')}`;
            app.emit('started', baseUrl);
            if (app.get('env') === 'development') {
                console.log('Web server listening at: %s', baseUrl);
                if (app.get('loopback-component-explorer')) {
                    let explorerPath = app.get('loopback-component-explorer').mountPath;
                    console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
                }
            }
        });
    }, (err) => {
        console.error(`Error: ${err.message}`);
    });
}<commit_msg>add possibility to specify port for app<commit_after>'use strict';

var fs = require('fs');
var path = require('path');
var http = require('http');
var https = require('https');
var flags = require('node-flags');
var app = module.exports = require('./server/server');
var logger = require('./common/utils/logger');

if (require.main === module) {

    app.boot()
    .then(() => {
        let httpOnly = process.env.HTTP;
        let port =  flags.get('port') || app.get('port');

        if (typeof httpOnly === 'undefined') httpOnly = false;
        let server = null;
        if (httpOnly) {
            server = http.createServer(app);
        } else {
            let options = {
                key: fs.readFileSync(path.join('.', 'private', 'privatekey.pem')),
                cert: fs.readFileSync(path.join('.', 'private', 'certificate.pem'))
            };
            server = https.createServer(options, app);
        }
        server.listen(port, () => {
            let baseUrl = `${httpOnly ? 'http' : 'https'}://${app.get('host')}:${port}`;
            app.emit('started', baseUrl);
            if (app.get('env') === 'development') {
                console.log('Web server listening at: %s', baseUrl);
                if (app.get('loopback-component-explorer')) {
                    let explorerPath = app.get('loopback-component-explorer').mountPath;
                    console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
                }
            }
        });
    }, (err) => {
        if (NODE_ENV === 'development') {
            logger.error(`Error: ${err.stack}`);
        } else {
            logger.error(`Error: ${err.message}`);
        }
    });
}
<|endoftext|>


<commit_before>const { withDb } = Msa.require("db")
const { userMdw, unauthHtml } = Msa.require("user")
const MsaSheet = Msa.require("sheet/module")

class MsaPageModule extends Msa.Module {

	constructor() {
		super()
		this.initApp()
		this.initSheetMod()
	}

	initApp() {

		this.app.get("/:id", userMdw, async (req, res, next) => {
			withDb(async db => {
				const ctx = newCtx(req, { db })
				const reqId = req.params.id
				const id = this.sheetMod.getId(ctx, reqId)
				const sheet = await this.sheetMod.getSheet(ctx, id)
				if (sheet === null) return next(404)
				res.sendPage(this.sheetMod.renderSheetAsHtml(sheet, "/page/_sheet", reqId))
			}).catch(err => {
				if (err === Msa.FORBIDDEN) res.sendPage(unauthHtml)
				else next(err)
			})
		})
	}

	initSheetMod() {
		this.sheetMod = new class extends MsaSheet {
			getId(ctx, reqId) {
				return `page-${reqId}`
			}
		}
		this.app.use("/_sheet", this.sheetMod.app)
	}
}

/*
// register page sheets
msaSheet.registerType("page", {
	perms: {
		create: { group: "admin" }
	},
	content: {
		tag: "msa-sheet-boxes",
		content: {
			tag: "msa-sheet-text"
		}
	}
})
*/

// utils

function newCtx(req, kwargs) {
	const ctx = Object.create(req)
	Object.assign(ctx, kwargs)
	return ctx
}

// export

module.exports = new MsaPageModule()
<commit_msg>404 => Msa.NOT_FOUND<commit_after>const { withDb } = Msa.require("db")
const { userMdw, unauthHtml } = Msa.require("user")
const MsaSheet = Msa.require("sheet/module")

class MsaPageModule extends Msa.Module {

	constructor() {
		super()
		this.initApp()
		this.initSheetMod()
	}

	initApp() {

		this.app.get("/:id", userMdw, async (req, res, next) => {
			withDb(async db => {
				const ctx = newCtx(req, { db })
				const reqId = req.params.id
				const id = this.sheetMod.getId(ctx, reqId)
				const sheet = await this.sheetMod.getSheet(ctx, id)
				if (sheet === null) return next(Msa.NOT_FOUND)
				res.sendPage(this.sheetMod.renderSheetAsHtml(sheet, "/page/_sheet", reqId))
			}).catch(err => {
				if (err === Msa.FORBIDDEN) res.sendPage(unauthHtml)
				else next(err)
			})
		})
	}

	initSheetMod() {
		this.sheetMod = new class extends MsaSheet {
			getId(ctx, reqId) {
				return `page-${reqId}`
			}
		}
		this.app.use("/_sheet", this.sheetMod.app)
	}
}

/*
// register page sheets
msaSheet.registerType("page", {
	perms: {
		create: { group: "admin" }
	},
	content: {
		tag: "msa-sheet-boxes",
		content: {
			tag: "msa-sheet-text"
		}
	}
})
*/

// utils

function newCtx(req, kwargs) {
	const ctx = Object.create(req)
	Object.assign(ctx, kwargs)
	return ctx
}

// export

module.exports = new MsaPageModule()
<|endoftext|>


<commit_before>'use strict';

/**
 * @fileOverview sklad.
 */

var Response = require('Response');
var EventEmitter = require('EventEmitter');

var sklad;

function Sklad() {
    EventEmitter.call(this);
}

Sklad.prototype = new EventEmitter();

Sklad.prototype.constructor = Sklad;

Sklad.prototype.store = checkAndGetStorage();

/**
 * Проверяет доступность localStorage
 * @return {Boolean}
 */
Sklad.prototype.isSupported = function () {
    return !!this.store;
};

/**
 * Устанавливаем значение в localStorage
 * @param {*} key строка или все что может быть приведенно к строке
 *                ключ в localStorage для сохраняемого значения
 * @param {*} value сохраняемое значение
 * @param {Boolean} [strict] флаг для проверки сохраненного значения
 * @return {Response}
 */
Sklad.prototype.set = function (key, value, strict) {
    var r = new Response();
    var queue = new Response.Queue();
    var stringValue = JSON.stringify(value);

    if (!stringValue) {
        return r.reject("Bad param - value");
    }

    queue
        .setData('params', {
            key: key,
            val: stringValue
        })
        .push(setItem);

    if (strict) {
        queue
            .push(getItem)
            .push(checkSavedItem);
    } else {
        queue.push(queue.resolve);
    }

    queue.start();

    return r.listen(queue);
};

/**
 * Достаём значение из localStorage
 * @param {*} key строка или все что может быть приведенно к строке
 *                ключ сохраненного значения в localStorage
 * @param {String} [type] если указан как 'json' парсим значение
 *                      если нет отдаём строку
 * @return {Response}
 */
Sklad.prototype.get = function (key, type) {
    var r = new Response();
    var queue = new Response.Queue();

    queue
        .setData('params', {
            key: key,
            type: type
        })
        .push(getItem);

    if (type) {
        queue.push(parseData);
    }

    queue
        .push(queue.resolve)
        .start();

    return r.listen(queue);
};

/**
 * Метод для получения непосредственно значения
 * а не экземпляра Response
 * @param {*} key строка или все что может быть приведенно к строке
 *                ключ сохраненного значения в localStorage
 * @param {String} [type] если указан как 'json' парсим значение
 *                      если нет отдаём строку
 * @return {*} значение из localStorage
 */
Sklad.prototype.getValue = function (key, type) {
    return this
        .get(key, type)
        .getResult();
};

/**
 * Удаляем итэм из хранилища
 * @param {String} key ключ сохранённого значения
 * @return {Response}
 */
Sklad.prototype.remove = function (key) {
    var r = new Response();

    r.invoke(storage.removeItem, [key], storage);

    if (!r.isRejected()) {
        r.resolve();
    }

    return r;
}

sklad = new Sklad();

/**
 * @param {Object} event Объект события изменение значений в localStorage
 */
var triggerChangeEvent = function (event) {
    var event = 'change:' + event.key;

    this.emit(event, event.newValue);
}.bind(sklad);

/**
 * Подписываемся на изменение значений в localStorage
 * из других вкладок браузера
 */
 subscribeToStorage(triggerChangeEvent);

module.exports = sklad;

/**
 * Проверяем наличие localStorage
 * если доступен возвращаем ссылку на него
 * если не доступен возвращаем false
 * @return {Storage|Boolean}
 */
function checkAndGetStorage() {
    var test = new Date();
    var _storage;
    var result;

    try {
        (_storage = window.localStorage).setItem(test, test);
        result = _storage.getItem(test) == test;
        _storage.removeItem(test);
        return result && _storage;
    } catch (error) {
        return false;
    }
}

/**
 * В зависимости от браузера возвращаем нужную функцию для подписки на событие 'storage'
 * @return {Function}
 */
function subscribeToStorage(handler) {
    if ('v'=='\v') {
        // Note: IE listens on document
        document.attachEvent('onstorage', handler);
    } else if (window.opera || /webkit/i.test( navigator.userAgent )){
        // Note: Opera and WebKits listens on window
        return window.addEventListener('storage', handler, false);
    } else {
        // Note: FF listens on document.body or document
        return document.body.addEventListener('storage', handler, false);
    }
}

/**
 * Безопасный метод для записи в localStorage
 * если ловим ошибки реджектим очередь
 * @this {Queue}
 */
function setItem() {
    var params = this.getData('params');

    this.invoke(storage.setItem, [params.key, params.val], storage);
}

/**
 * Безопасный метод для чтения из localStorage
 * @this {Queue}
 * @return {*} значение из localStorage
 */
function getItem() {
    var params = this.getData('params');

    return this.invoke(storage.getItem, [params.key], storage);
}

/**
 * В зависимости от ожидаемого формата ответа парсим строку в json
 * или отдаём сырые данные
 * @this {Queue}
 * @return {*}
 */
function parseData(data) {
    var params = this.getData('params');

    if (params.type === 'json') {
        return this.invoke(JSON.parse, [data]);
    } else {
        return data;
    }
}

/**
 * Проверяем сохраненное значение если указан strict режим
 * @this {Queue}
 */
function checkSavedItem(data) {
    var params = this.getData('params');

    if (data !== params.val) {
        this.reject();
    } else {
        this.resolve(data);
    }
}
<commit_msg>small fix<commit_after>'use strict';

/**
 * @fileOverview sklad.
 */

var Response = require('Response');
var EventEmitter = require('EventEmitter');

var sklad;

function Sklad() {
    EventEmitter.call(this);
}

Sklad.prototype = new EventEmitter();

Sklad.prototype.constructor = Sklad;

Sklad.prototype.store = checkAndGetStorage();

/**
 * Проверяет доступность localStorage
 * @return {Boolean}
 */
Sklad.prototype.isSupported = function () {
    return !!this.store;
};

/**
 * Устанавливаем значение в localStorage
 * @param {*} key строка или все что может быть приведенно к строке
 *                ключ в localStorage для сохраняемого значения
 * @param {*} value сохраняемое значение
 * @param {Boolean} [strict] флаг для проверки сохраненного значения
 * @return {Response}
 */
Sklad.prototype.set = function (key, value, strict) {
    var r = new Response();
    var queue = new Response.Queue();
    var stringValue = JSON.stringify(value);

    if (!stringValue) {
        return r.reject("Bad param - value");
    }

    queue
        .setData('params', {
            key: key,
            val: stringValue
        })
        .push(setItem);

    if (strict) {
        queue
            .push(getItem)
            .push(checkSavedItem);
    } else {
        queue.push(queue.resolve);
    }

    queue.start();

    return r.listen(queue);
};

/**
 * Достаём значение из localStorage
 * @param {*} key строка или все что может быть приведенно к строке
 *                ключ сохраненного значения в localStorage
 * @param {String} [type] если указан как 'json' парсим значение
 *                      если нет отдаём строку
 * @return {Response}
 */
Sklad.prototype.get = function (key, type) {
    var r = new Response();
    var queue = new Response.Queue();

    queue
        .setData('params', {
            key: key,
            type: type
        })
        .push(getItem);

    if (type) {
        queue.push(parseData);
    }

    queue
        .push(queue.resolve)
        .start();

    return r.listen(queue);
};

/**
 * Метод для получения непосредственно значения
 * а не экземпляра Response
 * @param {*} key строка или все что может быть приведенно к строке
 *                ключ сохраненного значения в localStorage
 * @param {String} [type] если указан как 'json' парсим значение
 *                      если нет отдаём строку
 * @return {*} значение из localStorage
 */
Sklad.prototype.getValue = function (key, type) {
    return this
        .get(key, type)
        .getResult();
};

/**
 * Удаляем итэм из хранилища
 * @param {String} key ключ сохранённого значения
 * @return {Response}
 */
Sklad.prototype.remove = function (key) {
    var r = new Response();

    r.invoke(storage.removeItem, [key], storage);

    if (!r.isRejected()) {
        r.resolve();
    }

    return r;
}

sklad = new Sklad();

/**
 * @param {Object} event Объект события изменение значений в localStorage
 */
var triggerChangeEvent = function (event) {
    var event = 'change:' + event.key;

    this.emit(event, event.newValue);
}.bind(sklad);

/**
 * Подписываемся на изменение значений в localStorage
 * из других вкладок браузера
 */
subscribeToStorage(triggerChangeEvent);

module.exports = sklad;

/**
 * Проверяем наличие localStorage
 * если доступен возвращаем ссылку на него
 * если не доступен возвращаем false
 * @return {Storage|Boolean}
 */
function checkAndGetStorage() {
    var test = new Date();
    var _storage;
    var result;

    try {
        (_storage = window.localStorage).setItem(test, test);
        result = _storage.getItem(test) == test;
        _storage.removeItem(test);
        return result && _storage;
    } catch (error) {
        return undefined;
    }
}

/**
 * В зависимости от браузера возвращаем нужную функцию для подписки на событие 'storage'
 * @return {Function}
 */
function subscribeToStorage(handler) {
    if ('v'=='\v') {
        // Note: IE listens on document
        document.attachEvent('onstorage', handler);
    } else if (window.opera || /webkit/i.test( navigator.userAgent )){
        // Note: Opera and WebKits listens on window
        return window.addEventListener('storage', handler, false);
    } else {
        // Note: FF listens on document.body or document
        return document.body.addEventListener('storage', handler, false);
    }
}

/**
 * Безопасный метод для записи в localStorage
 * если ловим ошибки реджектим очередь
 * @this {Queue}
 */
function setItem() {
    var params = this.getData('params');

    this.invoke(storage.setItem, [params.key, params.val], storage);
}

/**
 * Безопасный метод для чтения из localStorage
 * @this {Queue}
 * @return {*} значение из localStorage
 */
function getItem() {
    var params = this.getData('params');

    return this.invoke(storage.getItem, [params.key], storage);
}

/**
 * В зависимости от ожидаемого формата ответа парсим строку в json
 * или отдаём сырые данные
 * @this {Queue}
 * @return {*}
 */
function parseData(data) {
    var params = this.getData('params');

    if (params.type === 'json') {
        return this.invoke(JSON.parse, [data]);
    } else {
        return data;
    }
}

/**
 * Проверяем сохраненное значение если указан strict режим
 * @this {Queue}
 */
function checkSavedItem(data) {
    var params = this.getData('params');

    if (data !== params.val) {
        this.reject();
    } else {
        this.resolve(data);
    }
}
<|endoftext|>


<commit_before>// ## Directive joystick
// @author Alex Suslov <suslov@me.com>
'use strict';
var lamda = function(ng){
  var Direction = {
    up    : 0b1000,
    down  : 0b0100,
    left  : 0b0010,
    right : 0b0001
  }

  function controller($scope, $element){
    var direction = 0;
    var force = false;
    var config = $scope.ngModel.widgetConfig;

    $scope.fill = {
      up    : config.fill,
      down  : config.fill,
      left  : config.fill,
      right : config.fill
    };

    $scope.btn = {
      up    : config.arrow,
      down  : config.arrow,
      left  : config.arrow,
      right : config.arrow
    };

    function pub(){ if (direction) $scope.ngModel.pub(direction.toString()); }

    var t = setInterval(function(){
      if(!force) pub();
      else force = false;
    }, config.delay);

    $scope.onRelease = function(name){
      $scope.btn[name] = config.arrow;
      $scope.fill[name] = config.fill;
      direction = Direction[name] ? Direction[name] ^ direction : 0
    }

    $scope.onTouch = function(name){
      $scope.btn[name] = config.arrowPressed;
      $scope.fill[name] = config.fillPressed;
      direction = Direction[name] ? Direction[name] | direction : 0
      pub();
      force = true;
    }
  }

  ng.module( 'widgets', [])
  .directive( 'joystickBtn4', function( ){
    return {
      scope:{ngModel:"="},
      controller: controller,
      templateUrl:'/lib/joystick-btn/joystick_btn4.svg'
    }
  })
  .directive( 'joystickBtn2', function(  ){
    return {
      scope:{ngModel:"="},
      controller: controller,
      templateUrl:'/lib/joystick-btn/joystick_btn2.svg'
    }
  })
  .directive( 'joystickBtn2_', function(  ){
    return {
      scope:{ngModel:"="},
      controller: controller,
      templateUrl:'/lib/joystick-btn/joystick_btn2_.svg'
    }
  })
}
lamda(angular);

<commit_msg>clean<commit_after>// ## Directive joystick
// @author Alex Suslov <suslov@me.com>
'use strict';
var lamda = function(ng){
  var Direction = {
    up    : 0b1000,
    down  : 0b0100,
    left  : 0b0010,
    right : 0b0001
  }

  function controller($scope, $element){
    var direction = 0;
    var force = false;
    var config = $scope.ngModel.widgetConfig;

    $scope.fill = {
      up    : config.fill,
      down  : config.fill,
      left  : config.fill,
      right : config.fill
    };

    $scope.btn = {
      up    : config.arrow,
      down  : config.arrow,
      left  : config.arrow,
      right : config.arrow
    };

    function pub(){ if (direction) $scope.ngModel.pub(direction.toString()); }

    var t = setInterval(function(){
      if(!force) pub();
      else force = false;
    }, config.delay);

    $scope.onRelease = function(name){
      $scope.btn[name] = config.arrow;
      $scope.fill[name] = config.fill;
      direction = Direction[name] ? Direction[name] ^ direction : 0
    }

    $scope.onTouch = function(name){
      $scope.btn[name] = config.arrowPressed;
      $scope.fill[name] = config.fillPressed;
      direction = Direction[name] ? Direction[name] | direction : 0
      pub();
      force = true;
    }
  }

  ng.module( 'widgets', [])
  .directive( 'joystickBtn4', function( ){
    return {
      scope:{ngModel:"="},
      controller: controller,
      templateUrl:'/lib/joystick-btn/joystick_btn4.svg'
    }
  })
  .directive( 'joystickBtn2', function(  ){
    return {
      scope:{ngModel:"="},
      controller: controller,
      templateUrl:'/lib/joystick-btn/joystick_btn2.svg'
    }
  })
  .directive( 'joystickBtn2_', function(  ){
    return {
      scope:{ngModel:"="},
      controller: controller,
      templateUrl:'/lib/joystick-btn/joystick_btn2_.svg'
    }
  })
}
lamda(angular);

<|endoftext|>