<commit_before>/**
 * Класс вычислений оставшегося времени для лотов
 * Время синхронезируется с сервером
 * @constructor
 * @dependencies {jQuery}
 * @param options {Object} - настройки
 * @param options.syncUrl {String} - URL для обновления серверного времени
 * @param options.syncLotsUrl {String} - URL для обновления данных лотов
 * @param options.durationMode {String} - настройка в значении 'ProlongationByLots' не дает загружать данне лотов
 */
function LotTimer(options) {

    this._syncUrl = options.syncUrl;
    this._syncLotsUrl = options.syncLotsUrl;
    this._durationMode = options.durationMode;
    this._intervalTimeOfSyncServerTime = options.syncInterval;
    this._intervalTimeOfUpdateLotsRemaindersTimers = 1000;

    this.syncServerTime();
    this.startRecursiveSyncServerTime();
    this.startTimerUpdateLotsRemaindersTime();
}

LotTimer.prototype = {

    /**
     * URL для синхронизации серверного времени
     */
    _syncUrl: '',

    /**
     * Серверное время
     * @type {timestamp}
     */
    _serverTime: null,

    /**
     * Остаток времени
     * Обновляеться с сервера переодически - корректируеться
     * А так же обновляется при выислении по таймеру
     * @type {timestamp}
     */
    _remainderTime: null,

    /**
     * Если параметр выставлен в "ProlongationByLots" - проиходит обновление лотов
     */
    _durationMode: null,

    /**
     * Идентификатор таймера синхронизации данных серверного времени
     */
    __timerServerTime: null,

    /**
     * Таймер обновления времени всех лотов
     */
    __timerOfUpdateLotsRemainderTime: null,

    /**
     * Хэш хранения данных о лотах в формате: {lotId: lotRemainderTime};
     */
    dataOfLots: {},

    /**
     * Возаращает URL со случайным параметром, чтобы не кешировать
     * ответ от сервера, даже если он не поддерживает ETag
     * @param url {String} - URL запроса
     * @private
     */
    _getURLWithCMD: function (url) {
        return url + '?' + Date.now();
    },

    /**
     * Запустить переодическое обновление серверного времени
     */
    startRecursiveSyncServerTime: function () {
        this.__timerServerTime = setTimeout(function () {
                this.syncServerTime();
                this.startRecursiveSyncServerTime();
            }.bind(this),
            this._intervalTimeOfSyncServerTime
        );
    },

    /**
     * Остановить переодическое побновление серверного времени
     */
    stopRecursiveSyncServerTime: function () {
        clearTimeout(this.__timerServerTime);
    },

    /**
     * Синхронезация времени с сервером
     */
    syncServerTime: function () {
        var startSyncTime = this.getPresentTime();

        $.get(this._getURLWithCMD(this._syncUrl))
            .done(this._parseResponseServerTime.bind(this, startSyncTime))
            .fail(this.signalServerResponseFail.bind(this));
    },

    _parseResponseServerTime: function (startSyncTime, response) {
        var timeSpentOnRequest;

        if ($.type(response) !== 'object' || !response.endTimeSpan) {
            this.signalServerResponseFail();
            return;
        }

        // вычисляеться время потраченное на отправление и полуение запроса от сервера
        // делиться на два - это интересно почему, видимо из практики вычитано
        timeSpentOnRequest = Math.floor((this.getPresentTime() - startSyncTime) / 2);

        this.__lastTimestampServerTimeLoad = this.getPresentTime();

        // Вычисляеться количество прошедщих секунд по вычелсенному времени потраченному на запрос
        // и добавляеться к серверному времени
        this._remainderTime = this.addSecondsTo(response.endTimeSpan, Math.round(timeSpentOnRequest / 1000));

        if (this._isTimeOver()) {
            this.signalTimeIsOver();
            return;
        }

        this.setServerTime(response.dateTime, timeSpentOnRequest);

    },

    /**
     * Сохранить серверное время
     * @param dataTime {Date}
     * @param timeSpentOnRequest {timestamp}
     */
    setServerTime: function (dataTime, timeSpentOnRequest) {

        if (dataTime) {
            this._serverTime = new Date(
                dataTime.Year,
                dataTime.Month - 1,
                dataTime.Day,
                dataTime.Hour,
                dataTime.Minute,
                dataTime.Second,
                dataTime.Millisecond + timeSpentOnRequest
            );

            this.signalServerTimeUpdated(this._serverTime);
        }

        if (this._durationMode === 'ProlongationByLots') {
            this.getLotsData();
        }
    },

    /**
     * Получение данных лотов
     */
    getLotsData: function () {
        $.get(this._getURLWithCMD(this._syncLotsUrl), function (response) {

            if (response && response.lotsEndTime) {
                this.__lastTimestampLoadDataLots = this.getPresentTime();
                this.resolveLotsTime(response.lotsEndTime);
            }

            this.stopTimerUpdateLotsRemaindersTime();
            this.startTimerUpdateLotsRemaindersTime();

        }.bind(this));
    },

    /**
     * Обновление данных времени лотов
     * @param data
     */
    resolveLotsTime: function (data) {
        this.dataOfLots = data;
    },

    /**
     * Сохранить оставшееся время в хэш лотов
     * Так же вызывает сигналы об обновлении лота
     * @param lotId {Number|String} - идентификатор лота
     * @param lotRemainderTime - остаток времени
     */
    setLotReminderTime: function (lotId, lotRemainderTime) {
        this.dataOfLots[lotId] = lotRemainderTime;
    },

    /**
     * Обновление времени для всех лотов в хеше
     * путем вычисления времени прошедшего с момента последнего обноавления с сервера,
     * до текущего момента
     */
    updateLotsRemaindersTime: function (cnt) {
        var lotId, lotRemainderTime;

        if (this.dataOfLots == null) {
            return;
        }

        for (lotId in this.dataOfLots) {

            if (!this.dataOfLots.hasOwnProperty(lotId)) {
                continue;
            }

            lotRemainderTime = this.dataOfLots[lotId];

            // отсчет добавления секунд ведеться от последнего обновления лотов
            // возможный косяк
            lotRemainderTime = this.addSecondsTo(lotRemainderTime, 0 - (cnt + 1));

            this.setLotReminderTime(lotId, lotRemainderTime);
        }

        this.signalLotsUpdated(this.dataOfLots);
    },

    startTimerUpdateLotsRemaindersTime: function () {
        var start = this.getPresentTime(),
            timeout = 1000;

        this.stopTimerUpdateLotsRemaindersTime();

        function updateTime () {
            var diff, cnt;

            diff = this.getPresentTime() - start - timeout;
            cnt = Math.floor(diff / timeout);

            if (this._serverTime) {
                this._serverTime.setSeconds(this._serverTime.getSeconds() + cnt + 1);
                this.signalServerTimeUpdated(this._serverTime);
            }

            if (this._remainderTime) {
                this.updateRemainingTime(this.addSecondsTo(this._remainderTime, 0 - (cnt + 1)));

                if (this._isTimeOver()) {
                    this.syncServerTime();
                }
            }

            //this.updateLotsRemaindersTime(cnt);

            diff = diff - timeout * cnt;
            start = this.getPresentTime() - diff;

            this.__timerOfUpdateLotsRemainderTime = setTimeout(
                updateTime.bind(this),
                (timeout - diff)
            );
        }

        updateTime.call(this);
    },

    updateRemainingTime: function (remainingTime) {
        this._remainderTime = remainingTime;
        this.signalRemainingTimeUpdate(this._remainderTime, this.dataOfLots);
    },

    stopTimerUpdateLotsRemaindersTime: function () {
        clearTimeout(this.__timerOfUpdateLotsRemainderTime);
    },

    /**
     * Проверка завершены ли торги или нет?
     * @param remainingTime {timestamp}
     * @private
     * @return {Boolean}
     */
    _isTimeOver: function (remainingTime) {
        var _remainingTime = remainingTime || this._remainderTime;

        return _remainingTime.Hours <= 0
            && _remainingTime.Minutes <= 0
            && _remainingTime.Seconds <= 0;
    },

    /**
     * Сигнал о завершении торгов
     * @override
     */
    signalTimeIsOver: function () { },

    /**
     * Запрос к серверу для получения серверного времени обвалился или пришел неверный ответ
     * @override
     */
    signalServerResponseFail: function () { },

    /**
     * Сигнал о завершении оновления серверного времени
     * @param serverTime {Date} Серверное время
     * @override
     */
    signalServerTimeUpdated: function (serverTime) {
        //this.showServerTime();
        //this.showRemainingTime();
    },

    /**
     * Сигнал обновления остаточного времени
     * @param remainingTime {Object} - остаточное время
     * @override
     */
    signalRemainingTimeUpdate: function (remainingTime, dataLots) {

    },

    /**
     * Завершено обновление времени лотов
     * @param hashLotsTime
     */
    signalLotsUpdated: function (hashLotsTime) {

    },

    /**
     * Изменение оставшегося времени отсносительно времени отсчета (старта)
     * (Вычитание)
     * @param timeFrom {timestamp} - от какого времени вести отчет
     * @param remainderTime {timestamp} - время к которому нужно прибавить количествопройденных секунд
     * @return {timestamp} - время сколько осталось
     */
    getRemainderTime: function (timeFrom, remainderTime) {
        var start, diffTime, diffSec;

        start = timeFrom;

        // сколько прошло времени с последнего запроса лотов
        diffTime = (this.getPresentTime() - start);

        // количество секунд с прошедших за это время
        diffSec = Math.floor(diffTime / 1000);

        // расчет прошедших секунд со времени старта
        // вычитаем из времени старта, для получения времени оставшегося
        return this.addSecondsTo(remainderTime, 0 - (diffSec + 1));
    },

    /**
     * Получить текущее время
     */
    getPresentTime: function () {
        return new Date().getTime();
    },

    /**
     * Добавление секунд к указанному времени
     * @param timeSpan {timestamp} указанное время
     * @param s {Number} сколько секунд надо добавить
     * @returns {timestamp}
     */
    addSecondsTo: function (timeSpan, s) {

        var ts = timeSpan.Hours * 3600;
        ts += timeSpan.Minutes * 60;
        ts += timeSpan.Seconds;

        ts += s;

        var h = ~~(ts / 3600);
        ts = ts % 3600;
        var m = ~~(ts / 60);
        ts = ts % 60;

        timeSpan.Hours = h;
        timeSpan.Minutes = m;
        timeSpan.Seconds = ts;
        return timeSpan;
    }
};

/**
 * Кнотроллер Виджета серверного времени
 * @constructor
 */
function ServerClock(options) {

    this.defaults = {
        mode: 'member',
        syncUrl: window.realEndTimeSyncUrl + '/' + window.auctionId,
        syncLotsUrl: window.realLotEndTimeSyncUrl + '/' + window.auctionId,
        durationMode: window.durationMode ? window.durationMode : 'none',
        syncInterval: 60000,
        processLotsSelector: "#lots-table tr[id]:not(.lot-disable)",
        serverTimeSelector: "#server-clock",
        remainingTimeSelector: "#auction-end-clock, .auction-end-clock ",
        remainingTimeLotSelector: '#lot-end-clock-',
        lotStatusSelector: '#lot-status-',
        lotStatusActiveText: 'Аукцион по позиции продолжается',
        lotStatusStopText: 'Аукцион по позиции завершен'
    };

    options = options || {};
    this.options = $.extend(this.defaults, options);
}

ServerClock.prototype = {

    init: function () {
        var options = this.options;

        if (this.options.mode == "org") {
            $('#bootstrap_alert').appendAlert(this.options.message + ')', 'warning');
        }

        this.$serverTimeElement = $(this.options.serverTimeSelector);
        this.$remainingTimeElement = $(this.options.remainingTimeSelector);

        this.lotTimer = new LotTimer(this.options);

        this.lotTimer.signalServerTimeUpdated = function (serverTime) {
            $(options.serverTimeSelector)
                .text(getClockString(serverTime.getHours(), serverTime.getMinutes(), serverTime.getSeconds()));
        };

        this.lotTimer.signalRemainingTimeUpdate = function (remainingTime, dataLots) {
            var lotId, i, lotRemainderTime, lot;
            var timeText = getClockString(
                remainingTime.Hours,
                remainingTime.Minutes,
                remainingTime.Seconds
            );

            $(options.remainingTimeSelector)
                .text(timeText);

            if (dataLots) {

                i = dataLots.length;

                for (; i >= 0; i--) {

                    lot = dataLots[i];
                    //lotRemainderTime = lot.endTime;

                    $el = $(options.remainingTimeLotSelector + lot.lotId);

                    if (this._isTimeOver()) {
                        // время вышло
                        span = ['',
                            timeText,
                            ''].join('');
                    } else {
                        span = ['',
                            timeText,
                            ''].join('');
                    }

                    $el.html(span);
                }
            }
        }
    },

    getServerTime: function () {
        this.lotTimer.syncServerTime();
    }
};

function addZero(value) {
    if (value >= 0 && value <= 9) return '0' + value;
    return value;
}
function getSmallClockString(h, m) {
    return addZero(h) + ':' + addZero(m);
}
function getClockString(h, m, s) {
    return getSmallClockString(h, m) + ':' + addZero(s);
}<commit_msg>update sample second<commit_after>/**
 * Класс вычислений оставшегося времени для лотов
 * Время синхронезируется с сервером
 * @constructor
 * @dependencies {jQuery}
 * @param options {Object} - настройки
 * @param options.syncUrl {String} - URL для обновления серверного времени
 * @param options.syncLotsUrl {String} - URL для обновления данных лотов
 * @param options.durationMode {String} - настройка в значении 'ProlongationByLots' не дает загружать данне лотов
 */
function LotTimer(options) {

    this._syncUrl = options.syncUrl;
    this._syncLotsUrl = options.syncLotsUrl;
    this._durationMode = options.durationMode;
    this._intervalTimeOfSyncServerTime = options.syncInterval;
    this._intervalTimeOfUpdateLotsRemaindersTimers = 1000;

    this.syncServerTime();
    this.startRecursiveSyncServerTime();
    this.startTimerUpdateLotsRemaindersTime();
}

LotTimer.prototype = {

    /**
     * URL для синхронизации серверного времени
     */
    _syncUrl: '',

    /**
     * Серверное время
     * @type {timestamp}
     */
    _serverTime: null,

    /**
     * Остаток времени
     * Обновляеться с сервера переодически - корректируеться
     * А так же обновляется при выислении по таймеру
     * @type {timestamp}
     */
    _remainderTime: null,

    /**
     * Если параметр выставлен в "ProlongationByLots" - проиходит обновление лотов
     */
    _durationMode: null,

    /**
     * Идентификатор таймера синхронизации данных серверного времени
     */
    __timerServerTime: null,

    /**
     * Таймер обновления времени всех лотов
     */
    __timerOfUpdateLotsRemainderTime: null,

    /**
     * Хэш хранения данных о лотах в формате: {lotId: lotRemainderTime};
     */
    dataOfLots: {},

    /**
     * Возаращает URL со случайным параметром, чтобы не кешировать
     * ответ от сервера, даже если он не поддерживает ETag
     * @param url {String} - URL запроса
     * @private
     */
    _getURLWithCMD: function (url) {
        return url + '?' + Date.now();
    },

    /**
     * Запустить переодическое обновление серверного времени
     */
    startRecursiveSyncServerTime: function () {
        this.__timerServerTime = setTimeout(function () {
                this.syncServerTime();
                this.startRecursiveSyncServerTime();
            }.bind(this),
            this._intervalTimeOfSyncServerTime
        );
    },

    /**
     * Остановить переодическое побновление серверного времени
     */
    stopRecursiveSyncServerTime: function () {
        clearTimeout(this.__timerServerTime);
    },

    /**
     * Синхронезация времени с сервером
     */
    syncServerTime: function () {
        var startSyncTime = this.getPresentTime();

        $.get(this._getURLWithCMD(this._syncUrl))
            .done(this._parseResponseServerTime.bind(this, startSyncTime))
            .fail(this.signalServerResponseFail.bind(this));
    },

    _parseResponseServerTime: function (startSyncTime, response) {
        var timeSpentOnRequest;

        if ($.type(response) !== 'object' || !response.endTimeSpan) {
            this.signalServerResponseFail();
            return;
        }

        // вычисляеться время потраченное на отправление и полуение запроса от сервера
        // делиться на два - это интересно почему, видимо из практики вычитано
        timeSpentOnRequest = Math.floor((this.getPresentTime() - startSyncTime) / 2);

        this.__lastTimestampServerTimeLoad = this.getPresentTime();

        // Вычисляеться количество прошедщих секунд по вычелсенному времени потраченному на запрос
        // и добавляеться к серверному времени
        this._remainderTime = this.addSecondsTo(response.endTimeSpan, Math.round(timeSpentOnRequest / 1000));

        if (this._isTimeOver()) {
            this.signalTimeIsOver();
            return;
        }

        this.setServerTime(response.dateTime, timeSpentOnRequest);

    },

    /**
     * Сохранить серверное время
     * @param dataTime {Date}
     * @param timeSpentOnRequest {timestamp}
     */
    setServerTime: function (dataTime, timeSpentOnRequest) {

        if (dataTime) {
            this._serverTime = new Date(
                dataTime.Year,
                dataTime.Month - 1,
                dataTime.Day,
                dataTime.Hour,
                dataTime.Minute,
                dataTime.Second,
                dataTime.Millisecond + timeSpentOnRequest
            );

            this.signalServerTimeUpdated(this._serverTime);
        }

        if (this._durationMode === 'ProlongationByLots') {
            this.getLotsData();
        }
    },

    /**
     * Получение данных лотов
     */
    getLotsData: function () {
        $.get(this._getURLWithCMD(this._syncLotsUrl), function (response) {

            if (response && response.lotsEndTime) {
                this.__lastTimestampLoadDataLots = this.getPresentTime();
                this.resolveLotsTime(response.lotsEndTime);
            }

            this.stopTimerUpdateLotsRemaindersTime();
            this.startTimerUpdateLotsRemaindersTime();

        }.bind(this));
    },

    /**
     * Обновление данных времени лотов
     * @param data
     */
    resolveLotsTime: function (data) {
        this.dataOfLots = data;
    },

    /**
     * Сохранить оставшееся время в хэш лотов
     * Так же вызывает сигналы об обновлении лота
     * @param lotId {Number|String} - идентификатор лота
     * @param lotRemainderTime - остаток времени
     */
    setLotReminderTime: function (lotId, lotRemainderTime) {
        this.dataOfLots[lotId] = lotRemainderTime;
    },

    /**
     * Обновление времени для всех лотов в хеше
     * путем вычисления времени прошедшего с момента последнего обноавления с сервера,
     * до текущего момента
     */
    updateLotsRemaindersTime: function (cnt) {
        var lotId, lotRemainderTime;

        if (this.dataOfLots == null) {
            return;
        }

        for (lotId in this.dataOfLots) {

            if (!this.dataOfLots.hasOwnProperty(lotId)) {
                continue;
            }

            lotRemainderTime = this.dataOfLots[lotId];

            // отсчет добавления секунд ведеться от последнего обновления лотов
            // возможный косяк
            lotRemainderTime = this.addSecondsTo(lotRemainderTime, 0 - (cnt + 1));

            this.setLotReminderTime(lotId, lotRemainderTime);
        }

        this.signalLotsUpdated(this.dataOfLots);
    },

    startTimerUpdateLotsRemaindersTime: function () {
        var start = this.getPresentTime(),
            timeout = 1000;

        this.stopTimerUpdateLotsRemaindersTime();

        function updateTime () {
            var diff, cnt;

            diff = this.getPresentTime() - start - timeout;
            cnt = Math.floor(diff / timeout);

            if (this._serverTime) {
                this._serverTime.setSeconds(this._serverTime.getSeconds() + cnt + 1);
                this.signalServerTimeUpdated(this._serverTime);
            }

            if (this._remainderTime) {
                this.updateRemainingTime(this.addSecondsTo(this._remainderTime, 0 - (cnt + 1)));

                if (this._isTimeOver()) {
                    this.syncServerTime();
                }
            }

            //this.updateLotsRemaindersTime(cnt);

            diff = diff - timeout * cnt;
            start = this.getPresentTime() - diff;

            this.__timerOfUpdateLotsRemainderTime = setTimeout(
                updateTime.bind(this),
                (timeout - diff)
            );
        }

        updateTime.call(this);
    },

    updateRemainingTime: function (remainingTime) {
        this._remainderTime = remainingTime;
        this.signalRemainingTimeUpdate(this._remainderTime, this.dataOfLots);
    },

    stopTimerUpdateLotsRemaindersTime: function () {
        clearTimeout(this.__timerOfUpdateLotsRemainderTime);
    },

    /**
     * Проверка завершены ли торги или нет?
     * @param remainingTime {timestamp}
     * @private
     * @return {Boolean}
     */
    _isTimeOver: function (remainingTime) {
        var _remainingTime = remainingTime || this._remainderTime;

        return _remainingTime.Hours <= 0
            && _remainingTime.Minutes <= 0
            && _remainingTime.Seconds <= 0;
    },

    /**
     * Сигнал о завершении торгов
     * @override
     */
    signalTimeIsOver: function () { },

    /**
     * Запрос к серверу для получения серверного времени обвалился или пришел неверный ответ
     * @override
     */
    signalServerResponseFail: function () { },

    /**
     * Сигнал о завершении оновления серверного времени
     * @param serverTime {Date} Серверное время
     * @override
     */
    signalServerTimeUpdated: function (serverTime) {
        //this.showServerTime();
        //this.showRemainingTime();
    },

    /**
     * Сигнал обновления остаточного времени
     * @param remainingTime {Object} - остаточное время
     * @override
     */
    signalRemainingTimeUpdate: function (remainingTime, dataLots) {

    },

    /**
     * Завершено обновление времени лотов
     * @param hashLotsTime
     */
    signalLotsUpdated: function (hashLotsTime) {

    },

    /**
     * Изменение оставшегося времени отсносительно времени отсчета (старта)
     * (Вычитание)
     * @param timeFrom {timestamp} - от какого времени вести отчет
     * @param remainderTime {timestamp} - время к которому нужно прибавить количествопройденных секунд
     * @return {timestamp} - время сколько осталось
     */
    getRemainderTime: function (timeFrom, remainderTime) {
        var start, diffTime, diffSec;

        start = timeFrom;

        // сколько прошло времени с последнего запроса лотов
        diffTime = (this.getPresentTime() - start);

        // количество секунд с прошедших за это время
        diffSec = Math.floor(diffTime / 1000);

        // расчет прошедших секунд со времени старта
        // вычитаем из времени старта, для получения времени оставшегося
        return this.addSecondsTo(remainderTime, 0 - (diffSec + 1));
    },

    /**
     * Получить текущее время
     */
    getPresentTime: function () {
        return new Date().getTime();
    },

    /**
     * Добавление секунд к указанному времени
     * @param timeSpan {timestamp} указанное время
     * @param s {Number} сколько секунд надо добавить
     * @returns {timestamp}
     */
    addSecondsTo: function (timeSpan, s) {

        var ts = timeSpan.Hours * 3600;
        ts += timeSpan.Minutes * 60;
        ts += timeSpan.Seconds;

        ts += s;

        var h = ~~(ts / 3600);
        ts = ts % 3600;
        var m = ~~(ts / 60);
        ts = ts % 60;

        timeSpan.Hours = h;
        timeSpan.Minutes = m;
        timeSpan.Seconds = ts;
        return timeSpan;
    }
};

/**
 * Кнотроллер Виджета серверного времени
 * @constructor
 */
function ServerClock(options) {

    this.defaults = {
        mode: 'member',
        syncUrl: window.realEndTimeSyncUrl + '/' + window.auctionId,
        syncLotsUrl: window.realLotEndTimeSyncUrl + '/' + window.auctionId,
        durationMode: window.durationMode ? window.durationMode : 'none',
        syncInterval: 60000,
        processLotsSelector: "#lots-table tr[id]:not(.lot-disable)",
        serverTimeSelector: "#server-clock",
        remainingTimeSelector: "#auction-end-clock, .auction-end-clock ",
        remainingTimeLotSelector: '#lot-end-clock-',
        lotStatusSelector: '#lot-status-',
        lotStatusActiveText: 'Аукцион по позиции продолжается',
        lotStatusStopText: 'Аукцион по позиции завершен'
    };

    options = options || {};
    this.options = $.extend(this.defaults, options);
}

ServerClock.prototype = {

    init: function () {
        var options = this.options;

        if (this.options.mode == "org") {
            $('#bootstrap_alert').appendAlert(this.options.message + ')', 'warning');
        }

        this.$serverTimeElement = $(this.options.serverTimeSelector);
        this.$remainingTimeElement = $(this.options.remainingTimeSelector);

        this.lotTimer = new LotTimer(this.options);

        this.lotTimer.signalServerTimeUpdated = function (serverTime) {
            $(options.serverTimeSelector)
                .text(getClockString(serverTime.getHours(), serverTime.getMinutes(), serverTime.getSeconds()));
        };

        this.lotTimer.signalRemainingTimeUpdate = function (remainingTime, dataLots) {
            var len, i = 0, lotRemainderTime, lot;
            var timeText = getClockString(
                remainingTime.Hours,
                remainingTime.Minutes,
                remainingTime.Seconds
            );

            $(options.remainingTimeSelector)
                .text(timeText);

            if (dataLots) {

                len = dataLots.length;

                for (; i < len; i++) {

                    lot = dataLots[i];
                    //lotRemainderTime = lot.endTime;

                    $el = $(options.remainingTimeLotSelector + lot.lotId);

                    if (this._isTimeOver()) {
                        // время вышло
                        span = ['',
                            timeText,
                            ''].join('');
                    } else {
                        span = ['',
                            timeText,
                            ''].join('');
                    }

                    $el.html(span);
                }
            }
        }
    },

    getServerTime: function () {
        this.lotTimer.syncServerTime();
    }
};

function addZero(value) {
    if (value >= 0 && value <= 9) return '0' + value;
    return value;
}
function getSmallClockString(h, m) {
    return addZero(h) + ':' + addZero(m);
}
function getClockString(h, m, s) {
    return getSmallClockString(h, m) + ':' + addZero(s);
}<|endoftext|>


<commit_before>function Starfield ( game, params ) {
  var self = this,
      params = params || {};

  self.xLimit = game.canvas.offsetWidth;
  self.yLimit = game.canvas.offsetHeight;
  self.starCount = params.starCount || 100;

  $.extend(self, {
    init: function() {
      self.stars = [];

      for(var i = 0; i < self.starCount; i++) {
        self.stars.push(self.newStar());
        game.paper.fillStyle = rgb(self.stars[i].color, self.stars[i].color, self.stars[i].color);
      }
    },

    draw: function() {
      for(var i = 0; i < self.starCount; i++) {
        var star = self.stars[i];

        if ( star.y > self.yLimit ) {
         self.recycleStar(star);
        } else {
         star.y += star.acc;
        }

        self.drawStar(star);
      }
    },

    drawStar: function ( star ) {
      game.paper.fillStyle = rgb(star.color, star.color, star.color);
      game.paper.beginPath();
      game.paper.arc(star.x, star.y, star.z, 0, Math.PI*2, true);
      game.paper.fill();
    },

    newStar: function() {
      return {
        x: self.newX(),
        y: self.newY(),
        z: self.newZ(),
        acc: self.newAcc(),
        color: self.newColor()
      }
    },

    recycleStar: function ( star ) {
      star.x    = self.newX();
      star.y    = 0;
      star.z    = self.newZ();
      star.acc  = self.newAcc();
      star.color = self.newColor();
    },

    newX: function() {
      return random(0, self.xLimit);
    },

    newY: function() {
      return random(0, self.yLimit);
    },

    newZ: function() {
      return 1;
    },

    newAcc: function() {
      return random(1, 15);
    },

    newColor: function() {
      return random(0, 255);
    },

    toString: function() {
      return "Starfield#" + game.canvas.id;
    }
  });

  self.init();
};

function random(lower, upper){
  return Math.floor(Math.random() * (upper-lower) + lower);
}

function rgb (r, g, b) {
  return "rgb(" + r + "," + g + "," + b + ")";
}<commit_msg>jslint<commit_after>function Starfield ( game, params ) {
  var self = this,
      options = $.extend({
        starCount: 100
      }, params);

  self.xLimit = game.canvas.offsetWidth;
  self.yLimit = game.canvas.offsetHeight;
  self.starCount = options.starCount;

  $.extend(self, {
    init: function() {
      self.stars = [];

      for(var i = 0; i < self.starCount; i++) {
        self.stars.push(self.newStar());
        game.paper.fillStyle = rgb(self.stars[i].color, self.stars[i].color, self.stars[i].color);
      }
    },

    draw: function() {
      var i, star;
      for(i = 0; i < self.starCount; i++) {
        star = self.stars[i];

        if ( star.y > self.yLimit ) {
         self.recycleStar(star);
        } else {
         star.y += star.acc;
        }

        self.drawStar(star);
      }
    },

    drawStar: function ( star ) {
      game.paper.fillStyle = rgb(star.color, star.color, star.color);
      game.paper.beginPath();
      game.paper.arc(star.x, star.y, star.z, 0, Math.PI*2, true);
      game.paper.fill();
    },

    newStar: function() {
      return {
        x: self.newX(),
        y: self.newY(),
        z: self.newZ(),
        acc: self.newAcc(),
        color: self.newColor()
      };
    },

    recycleStar: function ( star ) {
      star.x    = self.newX();
      star.y    = 0;
      star.z    = self.newZ();
      star.acc  = self.newAcc();
      star.color = self.newColor();
    },

    newX: function() {
      return random(0, self.xLimit);
    },

    newY: function() {
      return random(0, self.yLimit);
    },

    newZ: function() {
      return 1;
    },

    newAcc: function() {
      return random(1, 15);
    },

    newColor: function() {
      return random(0, 255);
    },

    toString: function() {
      return "Starfield#" + game.canvas.id;
    }
  });

  self.init();
}

function random(lower, upper){
  return Math.floor(Math.random() * (upper-lower) + lower);
}

function rgb (r, g, b) {
  return "rgb(" + r + "," + g + "," + b + ")";
}<|endoftext|>


<commit_before>'use strict';

const Device = require('./device');

/**
 * Bulb Device.
 *
 * TP-Link models: LB100, LB110, LB120.
 * @extends Device
 * @extends EventEmitter
 */
class Bulb extends Device {
  /**
   * Created by {@link Client} - Do not instantiate directly.
   *
   * See {@link Device#constructor} for common options.
   * @see Device#constructor
   * @param  {Object} options
   */
  constructor (options) {
    super(options);
    if (typeof options === 'undefined') options = {};

    this.supportsEmeter = true;

    this.apiModuleNamespace = {
      'system': 'smartlife.iot.common.system',
      'cloud': 'smartlife.iot.common.cloud',
      'schedule': 'smartlife.iot.common.schedule',
      'timesetting': 'smartlife.iot.common.timesetting',
      'emeter': 'smartlife.iot.common.emeter',
      'netif': 'netif'
    };

    this.lightState = {};

    this.lastState = Object.assign(this.lastState, { powerOn: null, inUse: null });
  }

  get sysInfo () {
    return super.sysInfo;
  }

  /**
   * @private
   */
  set sysInfo (sysInfo) {
    super.sysInfo = sysInfo;
    this.emitEvents();
  }

  /**
   * @private
   */
  emitEvents () {
    if (!this.lightState) return;
    let powerOn = (this.lightState.on_off === 1);

    this.log.debug('emitEvents() powerOn: %s lastState: %j', powerOn, this.lastState);

    if (this.lastState.powerOn !== powerOn) {
      this.lastState.powerOn = powerOn;
      if (powerOn) {
        this.emit('power-on', this);
        this.emit('bulb-on', this);
      } else {
        this.emit('power-off', this);
        this.emit('bulb-off', this);
      }
    }

    // using JSON.stringify for now, need device to test actual results
    if (JSON.stringify(this.lastState.lightState) !== JSON.stringify(this.lightState)) {
      this.lastState.lightState = this.lightState;
      this.emit('bulb-change');
    }
  }
  /**
   * Get Bulb light state.
   *
   * Requests `lightingservice.get_light_state`.
   * @return {Promise<Object, ResponseError>} parsed JSON response
   */
  async getLightState () {
    this.lightState = await this.sendCommand('{"smartlife.iot.smartbulb.lightingservice":{"get_light_state":{}}}');
    this.emitEvents();
    return this.lightState;
  }
  /**
   * Sets Bulb light state (on/off, brightness, color, etc).
   *
   * Sends `lightingservice.transition_light_state` command.
   * @param  {Object}  options
   * @param  {number}  options.ignore_default    1=true, 0=false
   * @param  {number}  options.transition_period
   * @param  {number}  options.on_off            1=on, 0=off
   * @param  {string}  options.mode
   * @param  {number}  options.hue               0-360
   * @param  {number}  options.saturation        0-100
   * @param  {number}  options.brightness        0-100
   * @param  {number}  options.color_temp        0-7000 (Kelvin)
   * @return {Promise<boolean, ResponseError>}
   */
  async setLightState (options) {
    let state = {};
    state.ignore_default = options.ignore_default || 1;
    state.transition_period = options.transition_period || 0;
    if (options.on_off !== undefined) state.on_off = options.on_off;
    if (options.mode !== undefined) state.mode = options.mode;
    if (options.hue !== undefined) state.hue = options.hue;
    if (options.saturation !== undefined) state.saturation = options.saturation;
    if (options.brightness !== undefined) state.brightness = options.brightness;
    if (options.color_temp !== undefined) state.color_temp = options.color_temp;

    const payload = {
      'smartlife.iot.smartbulb.lightingservice': {
        'transition_light_state': state
      }
    };

    this.lightState = await this.sendCommand(payload);
    this.emitEvents();
    return true;
  }
  /**
   * Gets on/off state of Bulb.
   *
   * Requests `lightingservice.get_light_state` and returns true if `on_off === 1`.
   * @return {Promise<boolean, ResponseError>}
   */
  async getPowerState () {
    let lightState = await this.getLightState();
    return (lightState.on_off === 1);
  }
  /**
   * Sets on/off state of Bulb.
   *
   * Sends `lightingservice.transition_light_state` command with on_off `value`.
   * @param  {boolean}  value true: on, false: off
   * @return {Promise<boolean, ResponseError>}
   */
  async setPowerState (value) {
    return this.setLightState({on_off: (value ? 1 : 0)});
  }
}

module.exports = Bulb;
<commit_msg>getInfo<commit_after>'use strict';

const Device = require('./device');

/**
 * Bulb Device.
 *
 * TP-Link models: LB100, LB110, LB120.
 * @extends Device
 * @extends EventEmitter
 */
class Bulb extends Device {
  /**
   * Created by {@link Client} - Do not instantiate directly.
   *
   * See {@link Device#constructor} for common options.
   * @see Device#constructor
   * @param  {Object} options
   */
  constructor (options) {
    super(options);
    if (typeof options === 'undefined') options = {};

    this.supportsEmeter = true;

    this.apiModuleNamespace = {
      'system': 'smartlife.iot.common.system',
      'cloud': 'smartlife.iot.common.cloud',
      'schedule': 'smartlife.iot.common.schedule',
      'timesetting': 'smartlife.iot.common.timesetting',
      'emeter': 'smartlife.iot.common.emeter',
      'netif': 'netif'
    };

    this.lightState = {};

    this.lastState = Object.assign(this.lastState, { powerOn: null, inUse: null });
  }

  get sysInfo () {
    return super.sysInfo;
  }

  /**
   * @private
   */
  set sysInfo (sysInfo) {
    super.sysInfo = sysInfo;
    this.emitEvents();
  }

  /**
   * @private
   */
  emitEvents () {
    if (!this.lightState) return;
    let powerOn = (this.lightState.on_off === 1);

    this.log.debug('emitEvents() powerOn: %s lastState: %j', powerOn, this.lastState);

    if (this.lastState.powerOn !== powerOn) {
      this.lastState.powerOn = powerOn;
      if (powerOn) {
        this.emit('power-on', this);
        this.emit('bulb-on', this);
      } else {
        this.emit('power-off', this);
        this.emit('bulb-off', this);
      }
    }

    // using JSON.stringify for now, need device to test actual results
    if (JSON.stringify(this.lastState.lightState) !== JSON.stringify(this.lightState)) {
      this.lastState.lightState = this.lightState;
      this.emit('bulb-change');
    }
  /**
   * Requests common Bulb status details in a single request.
   * - `system.get_sysinfo`
   * - `cloud.get_sysinfo`
   * - `emeter.get_realtime`
   * - `schedule.get_next_action`
   * @return {Promise<Object, Error>} parsed JSON response
   */
  async getInfo () {
    // TODO switch to sendCommand, but need to handle error for devices that don't support emeter
    let data = await this.send(`{"${this.apiModuleNamespace.emeter}":{"get_realtime":{}},"${this.apiModuleNamespace.lightingservice}":{"get_light_state":{}},"${this.apiModuleNamespace.schedule}":{"get_next_action":{}},"${this.apiModuleNamespace.system}":{"get_sysinfo":{}},"${this.apiModuleNamespace.cloud}":{"get_info":{}}}`);
    this.sysInfo = data[this.apiModuleNamespace.system].get_sysinfo;
    this.cloudInfo = data[this.apiModuleNamespace.cloud].get_info;
    this.emeterRealtime = data[this.apiModuleNamespace.emeter].get_realtime;
    this.scheduleNextAction = data[this.apiModuleNamespace.schedule].get_next_action;
    this.lightState = data[this.apiModuleNamespace.lightingservice].get_light_state;
    return {sysInfo: this.sysInfo, cloudInfo: this.cloudInfo, emeterRealtime: this.emeterRealtime, scheduleNextAction: this.scheduleNextAction, lightState: this.lightState};
  }
  /**
   * Get Bulb light state.
   *
   * Requests `lightingservice.get_light_state`.
   * @return {Promise<Object, ResponseError>} parsed JSON response
   */
  async getLightState () {
    this.lightState = await this.sendCommand('{"smartlife.iot.smartbulb.lightingservice":{"get_light_state":{}}}');
    this.emitEvents();
    return this.lightState;
  }
  /**
   * Sets Bulb light state (on/off, brightness, color, etc).
   *
   * Sends `lightingservice.transition_light_state` command.
   * @param  {Object}  options
   * @param  {number}  options.ignore_default    1=true, 0=false
   * @param  {number}  options.transition_period
   * @param  {number}  options.on_off            1=on, 0=off
   * @param  {string}  options.mode
   * @param  {number}  options.hue               0-360
   * @param  {number}  options.saturation        0-100
   * @param  {number}  options.brightness        0-100
   * @param  {number}  options.color_temp        0-7000 (Kelvin)
   * @return {Promise<boolean, ResponseError>}
   */
  async setLightState (options) {
    let state = {};
    state.ignore_default = options.ignore_default || 1;
    state.transition_period = options.transition_period || 0;
    if (options.on_off !== undefined) state.on_off = options.on_off;
    if (options.mode !== undefined) state.mode = options.mode;
    if (options.hue !== undefined) state.hue = options.hue;
    if (options.saturation !== undefined) state.saturation = options.saturation;
    if (options.brightness !== undefined) state.brightness = options.brightness;
    if (options.color_temp !== undefined) state.color_temp = options.color_temp;

    const payload = {
      'smartlife.iot.smartbulb.lightingservice': {
        'transition_light_state': state
      }
    };

    this.lightState = await this.sendCommand(payload);
    this.emitEvents();
    return true;
  }
  /**
   * Gets on/off state of Bulb.
   *
   * Requests `lightingservice.get_light_state` and returns true if `on_off === 1`.
   * @return {Promise<boolean, ResponseError>}
   */
  async getPowerState () {
    let lightState = await this.getLightState();
    return (lightState.on_off === 1);
  }
  /**
   * Sets on/off state of Bulb.
   *
   * Sends `lightingservice.transition_light_state` command with on_off `value`.
   * @param  {boolean}  value true: on, false: off
   * @return {Promise<boolean, ResponseError>}
   */
  async setPowerState (value) {
    return this.setLightState({on_off: (value ? 1 : 0)});
  }
}

module.exports = Bulb;
<|endoftext|>


<commit_before>let test = require('tap').test
let parse = require('../lib/query-parser')

test('should extract CSS selector from query and keep it intact', t => {
  let query = '.home li > a'
  let result = parse(query)
  t.strictSame(result.selector, query)
  t.end()
})

test('should return null for `getter` if not present in the query', t => {
  let query = '.home li > a'
  let result = parse(query)
  t.strictSame(result.getter, null)
  t.end()
})

test('should return empty array for `filters` if not present in the query', t => {
  let query = '.home li > a'
  let result = parse(query)
  t.deepEqual(result.filters, [])
  t.end()
})

test('should extract `getter` from query if present', t => {
  let query = '.home li > a => href'
  let result = parse(query)
  t.strictSame(result.getter, 'href')
  t.end()
})

test('should ignore whitespace around and in between `getter`', t => {
  let query = `.home li > a =>   hr e
    f  `
  let result = parse(query)
  t.strictSame(result.getter, 'href')
  t.end()
})

test('should extract filter name', t => {
  let query = '.home li > a | trim'
  let result = parse(query)
  t.strictSame(result.filters[0].name, 'trim')
  t.end()
})

test('should extract all filter names in order', t => {
  let query = '.home li > a | trim | normalizeWhitespace'
  let result = parse(query)
  t.strictSame(result.filters[0].name, 'trim')
  t.strictSame(result.filters[1].name, 'normalizeWhitespace')
  t.end()
})
<commit_msg>Use strict to prevent failing tests on node 4<commit_after>'use strict'

let test = require('tap').test
let parse = require('../lib/query-parser')

test('should extract CSS selector from query and keep it intact', t => {
  let query = '.home li > a'
  let result = parse(query)
  t.strictSame(result.selector, query)
  t.end()
})

test('should return null for `getter` if not present in the query', t => {
  let query = '.home li > a'
  let result = parse(query)
  t.strictSame(result.getter, null)
  t.end()
})

test('should return empty array for `filters` if not present in the query', t => {
  let query = '.home li > a'
  let result = parse(query)
  t.deepEqual(result.filters, [])
  t.end()
})

test('should extract `getter` from query if present', t => {
  let query = '.home li > a => href'
  let result = parse(query)
  t.strictSame(result.getter, 'href')
  t.end()
})

test('should ignore whitespace around and in between `getter`', t => {
  let query = `.home li > a =>   hr e
    f  `
  let result = parse(query)
  t.strictSame(result.getter, 'href')
  t.end()
})

test('should extract filter name', t => {
  let query = '.home li > a | trim'
  let result = parse(query)
  t.strictSame(result.filters[0].name, 'trim')
  t.end()
})

test('should extract all filter names in order', t => {
  let query = '.home li > a | trim | normalizeWhitespace'
  let result = parse(query)
  t.strictSame(result.filters[0].name, 'trim')
  t.strictSame(result.filters[1].name, 'normalizeWhitespace')
  t.end()
})
<|endoftext|>


<commit_before>const express = require('express'),
      router = express.Router({mergeParams: true}),
      User = require('../models/user');
      Emoticon = require('../models/emoticon');

router.get('/', function(req, res, next) {
  User.findOne({username: req.params.username}).populate('emoticons').exec(function(err, user) {
    if (err) console.log(err);
    res.render('emoticons/index', {user, emoticons: user.emoticons});
  });
});

router.get('/new', isLoggedIn, function(req, res, next) {
  res.render('emoticons/new', {user: req.user});
});

router.post('/new', isLoggedIn, function(req, res, next) {
  User.findOne({username: req.user.username}).then(function(user) {
    Emoticon.create({content: req.body.content, category: req.body.category, user: user._id}).then(function(newEmoticon) {
      user.emoticons.push(newEmoticon);
      user.markModified('emoticons');
      user.save(function(err) {
        if (err) console.log(err);
        console.log('Saved!');
      });
      res.redirect(`/user/${user.username}/collection`);
    }).catch(function(err) {
      console.log(err);
    });
  });
});

router.post('/delete', isLoggedIn, function(req, res, next) {
  console.log(req.body.data.selectedArr);
  // remove from emoticons collection
  // Emoticons.remove({content: {$in: selected}});
  // splice out of user emoticons array
});

function isLoggedIn(req, res, next) {
  if (req.isAuthenticated()) {
    return next();
  }
  res.redirect('/login');
}

module.exports = router;
<commit_msg>Find user, remove selected emoticons, and splice them from user's array of emoticons<commit_after>const express = require('express'),
      router = express.Router({mergeParams: true}),
      User = require('../models/user');
      Emoticon = require('../models/emoticon');

router.get('/', function(req, res, next) {
  User.findOne({username: req.params.username}).populate('emoticons').exec(function(err, user) {
    if (err) console.log(err);
    res.render('emoticons/index', {user, emoticons: user.emoticons});
  });
});

router.get('/new', isLoggedIn, function(req, res, next) {
  res.render('emoticons/new', {user: req.user});
});

router.post('/new', isLoggedIn, function(req, res, next) {
  User.findOne({username: req.user.username}).then(function(user) {
    Emoticon.create({content: req.body.content, category: req.body.category, user: user._id}).then(function(newEmoticon) {
      user.emoticons.push(newEmoticon);
      user.markModified('emoticons');
      user.save(function(err) {
        if (err) console.log(err);
        console.log('Saved!');
      });
      res.redirect(`/user/${user.username}/collection`);
    }).catch(function(err) {
      console.log(err);
    });
  });
});

router.post('/delete', isLoggedIn, function(req, res, next) {
  var removeArr = req.body.data.selectedArr;
  var user = req.user;
  Emoticons.find({user}).remove({_id: {$in: removeArr}}, (err) => {
    if (err) {
      console.log(err);
    } else {
      console.log(`Successfully removed ${removeArr} from DB`);
    }
  });
  removeArr.forEach(function(id) {
    var index = user.emoticons.indexOf(id);
    if (index > -1) {
      user.emoticons.splice(index, 1);
    } else {
      console.log(`ID ${id} not found`);
    }
  });
});

function isLoggedIn(req, res, next) {
  if (req.isAuthenticated()) {
    return next();
  }
  res.redirect('/login');
}

module.exports = router;
<|endoftext|>


<commit_before>define([
    'opentype'
], function(
    opentype
) {
    "use strict";
    /*globals FileReader, XMLHttpRequest, console*/

    /**
     * Callback for when a fontfile has been loaded
     *
     * @param i: index of the font loaded
     * @param fontFileName
     * @param err: null, error-object or string with error message
     * @param fontArraybuffer
     */
    function onLoadFont(i, fontFileName, err, fontArraybuffer) {
        var font;
        /* jshint validthis: true */
        if(err) {
            console.warn('Can\'t load font', fontFileName, ' with error:', err);
            this.countAll--;
        }
        try {
            font = opentype.parse(fontArraybuffer);
            this.pubsub.publish('loadFont', i, fontFileName, font, fontArraybuffer);
            this.countLoaded += 1;
            // if there was an error loading a font (above) this will never run
        } catch (parseError) {
            console.warn('Can\'t load font', fontFileName, ' with error:', parseError);
            this.countAll--;
        }
        if(this.countLoaded === this.countAll) {
            this.pubsub.publish('allFontsLoaded', this.countAll);
        }
    }

    function loadFromUrl(fontInfo, callback) {
        var request = new XMLHttpRequest()
          , url = fontInfo.url
          ;
        request.open('get', url, true);
        request.responseType = 'arraybuffer';
        request.onload = function() {
            if (request.status !== 200) {
                return callback('Font could not be loaded: ' + request.statusText);
            }
            return callback(null, request.response);
        };
        request.send();
    }

    function fileInputFileOnLoad(callback, loadEvent) {
        /*jshint unused: vars, validthis:true*/
        callback(null, this.result);
    }
    function fileInputFileOnError(callback, loadEvent) {
        /*jshint unused: vars, validthis:true*/
        callback(this.error);
    }
    function loadFromFileInput(file, callback) {
        var reader = new FileReader();
        reader.onload = fileInputFileOnLoad.bind(reader, callback);
        reader.onerror = fileInputFileOnError.bind(reader, callback);
        reader.readAsArrayBuffer(file);
    }

    function loadFontsFromFileInput(pubsub, fileInputFiles) {
        _loadFonts(pubsub, fileInputFiles, loadFromFileInput);
    }
    loadFontsFromFileInput.needsPubSub = true;

    function loadFontsFromUrl(pubsub, fontFiles) {
        var i, l
          , fontInfo = []
          ;
        for(i=0,l=fontFiles.length;i<l;i++) {
            if (fontFiles[i]) {
                fontInfo.push({
                    name: fontFiles[i]
                    , url: fontFiles[i]
                });
            }
        }
        _loadFonts(pubsub, fontInfo, loadFromUrl);
    }

    function _loadFonts(pubsub, fontFiles, loadFont) {
        var i, l, fontInfo, onload
          , loaderState = {
                countLoaded: 0
              , countAll: fontFiles.length
              , pubsub: pubsub
            }
          ;

        for(i=0,l=fontFiles.length;i<l;i++) {
            fontInfo = fontFiles[i];
            pubsub.publish('prepareFont', i, fontInfo.name, l);
            onload = onLoadFont.bind(loaderState, i, fontInfo.name);
            // The timeout thing is handy to slow down the load progress,
            // if development is done on that part.
            // setTimeout(function(fontInfo, onload) {
            loadFont(fontInfo, onload);
            // }.bind(null, fontInfo, onload), Math.random() * 5000);
        }
    }

    return {
        fromUrl: loadFontsFromUrl
      , fromFileInput: loadFontsFromFileInput
    };
});
<commit_msg>Added error to loadFontsFromUrl when passed in URL is empty<commit_after>define([
    'opentype'
], function(
    opentype
) {
    "use strict";
    /*globals FileReader, XMLHttpRequest, console*/

    /**
     * Callback for when a fontfile has been loaded
     *
     * @param i: index of the font loaded
     * @param fontFileName
     * @param err: null, error-object or string with error message
     * @param fontArraybuffer
     */
    function onLoadFont(i, fontFileName, err, fontArraybuffer) {
        var font;
        /* jshint validthis: true */
        if(err) {
            console.warn('Can\'t load font', fontFileName, ' with error:', err);
            this.countAll--;
        }
        try {
            font = opentype.parse(fontArraybuffer);
            this.pubsub.publish('loadFont', i, fontFileName, font, fontArraybuffer);
            this.countLoaded += 1;
            // if there was an error loading a font (above) this will never run
        } catch (parseError) {
            console.warn('Can\'t load font', fontFileName, ' with error:', parseError);
            this.countAll--;
        }
        if(this.countLoaded === this.countAll) {
            this.pubsub.publish('allFontsLoaded', this.countAll);
        }
    }

    function loadFromUrl(fontInfo, callback) {
        var request = new XMLHttpRequest()
          , url = fontInfo.url
          ;
        request.open('get', url, true);
        request.responseType = 'arraybuffer';
        request.onload = function() {
            if (request.status !== 200) {
                return callback('Font could not be loaded: ' + request.statusText);
            }
            return callback(null, request.response);
        };
        request.send();
    }

    function fileInputFileOnLoad(callback, loadEvent) {
        /*jshint unused: vars, validthis:true*/
        callback(null, this.result);
    }
    function fileInputFileOnError(callback, loadEvent) {
        /*jshint unused: vars, validthis:true*/
        callback(this.error);
    }
    function loadFromFileInput(file, callback) {
        var reader = new FileReader();
        reader.onload = fileInputFileOnLoad.bind(reader, callback);
        reader.onerror = fileInputFileOnError.bind(reader, callback);
        reader.readAsArrayBuffer(file);
    }

    function loadFontsFromFileInput(pubsub, fileInputFiles) {
        _loadFonts(pubsub, fileInputFiles, loadFromFileInput);
    }
    loadFontsFromFileInput.needsPubSub = true;

    function loadFontsFromUrl(pubsub, fontFiles) {
        var i, l
          , fontInfo = []
          ;
        for(i=0,l=fontFiles.length;i<l;i++) {
            if (!fontFiles[i])
                throw new Error('The url at index '+i+' appears to be invalid.');
            fontInfo.push({
                name: fontFiles[i]
                , url: fontFiles[i]
            });
        }
        _loadFonts(pubsub, fontInfo, loadFromUrl);
    }

    function _loadFonts(pubsub, fontFiles, loadFont) {
        var i, l, fontInfo, onload
          , loaderState = {
                countLoaded: 0
              , countAll: fontFiles.length
              , pubsub: pubsub
            }
          ;

        for(i=0,l=fontFiles.length;i<l;i++) {
            fontInfo = fontFiles[i];
            pubsub.publish('prepareFont', i, fontInfo.name, l);
            onload = onLoadFont.bind(loaderState, i, fontInfo.name);
            // The timeout thing is handy to slow down the load progress,
            // if development is done on that part.
            // setTimeout(function(fontInfo, onload) {
            loadFont(fontInfo, onload);
            // }.bind(null, fontInfo, onload), Math.random() * 5000);
        }
    }

    return {
        fromUrl: loadFontsFromUrl
      , fromFileInput: loadFontsFromFileInput
    };
});
<|endoftext|>


<commit_before>var express = require('express');
var employees = express.Router();
var log = require('../lib/log');

var EmployeeObject = require('../models/employee-object').EmployeeObject;
var getEmployeeList = require('../lib/active-employee-list').getEmployeeList;

employees.get('/', function (req, res) {
  res.send('need a resource view');
});

employees.get('/today', function (req, res) {
  var today = new Date();
  var y = today.getUTCFullYear();
  var m = today.getUTCMonth() + 1;
  var d = today.getUTCDate();
  res.redirect('year' + y + '/month' + m + '/day' + d);
});

employees.get('/year/:y/month/:m/day/:d', function (req, res) {
  res.send('need a view here');
});

employees.get('/year/:y/month/:m/day/:d/json', function (req, res) {
  EmployeeObject.findOne({
    year: Number(req.params.y),
    month: Number(req.params.m),
    day: Number(req.params.d)
  }, function (err, list) {
    if (err) {
      log.error(err);
      return res.status(500).send(err.message);
    }
    if (list) {
      return res.json(list);
    }
    // check if today
    return res.status(404).send('cannot find the list for ' + req.params.y + '-' + req.params.m + '-' + req.params.d + '.');
  });
});

employees.post('/now', function (req, res) {
  getEmployeeList(true, function (err, response, list) {
    if (err) {
      log.error(err);
    }
    if (list) {
      return res.json(list);
    }
    res.status(500).send(err.message);
  });
});

module.exports = employees;
<commit_msg>update api<commit_after>var express = require('express');
var employees = express.Router();
var log = require('../lib/log');

var EmployeeObject = require('../models/employee-object').EmployeeObject;
var getEmployeeList = require('../lib/active-employee-list').getEmployeeList;

employees.get('/', function (req, res) {
  res.send('need a resource view');
});

employees.get('/today', function (req, res) {
  var today = new Date();
  var y = today.getUTCFullYear();
  var m = today.getUTCMonth() + 1;
  var d = today.getUTCDate();
  res.redirect('year' + y + '/month' + m + '/day' + d);
});

employees.get('/year/:y/month/:m/day/:d', function (req, res) {
  res.send('need a view here');
});

employees.get('/year/:y/month/:m/day/:d/json', function (req, res) {
  EmployeeObject.findOne({
    year: Number(req.params.y),
    month: Number(req.params.m),
    day: Number(req.params.d)
  }, function (err, list) {
    if (err) {
      log.error(err);
      return res.status(500).send(err.message);
    }
    if (list) {
      return res.json(list);
    }
    // check if today
    return res.status(404).send('cannot find the list for ' + req.params.y + '-' + req.params.m + '-' + req.params.d + '.');
  });
});

employees.post('/now', function (req, res) {
  getEmployeeList(true, function (err, list) {
    if (err) {
      log.error(err);
    }
    if (list) {
      return res.json(list);
    }
    res.status(500).send(err.message);
  });
});

module.exports = employees;
<|endoftext|>


<commit_before>'use strict'

// import modules
import init from './component/init'
import create from './element/create'
import insert from './component/insert'
import classify from './component/classify'
// import components
import Layout from './layout'

let defaults = {
  prefix: 'material',
  class: 'card'
}

class Card {
  /**
   * Constructor
   * @param  {Object} options - Component options
   * @return {Object} Class instance
   */
  constructor (options) {
    this.options = Object.assign({}, defaults, options || {})

    this.init()
    this.build()
  }

  init () {
    Object.assign(this, insert)
  }

  /**
   * build the component using the super method
   * @return {Object} The class instance
   */
  build () {
    var tag = this.options.tag || 'div'

    this.wrapper = create(tag)
    classify(this.wrapper, this.options)

    this.layout = new Layout(this.options.layout, this.wrapper)
  }
}

export default Card
<commit_msg>Fix and clean card<commit_after>'use strict'

// import modules
import init from './component/init'
import create from './element/create'
import insert from './component/insert'
import classify from './component/classify'
// import components
import Layout from './layout'

let defaults = {
  prefix: 'material',
  class: 'card',
  tag: 'div'
}

class Card {
  /**
   * Constructor
   * @param  {Object} options - Component options
   * @return {Object} Class instance
   */
  constructor (options) {

    this.init(options)
    this.build()
  }

  init (options) {
    this.options = Object.assign({}, defaults, options || {})
    Object.assign(this, insert)
  }

  /**
   * build the component using the super method
   * @return {Object} The class instance
   */
  build () {

    this.wrapper = create(this.options.tag)
    classify(this.wrapper, this.options)

    this.layout = new Layout(this.options.layout, this.wrapper)
  }
}

export default Card
<|endoftext|>


<commit_before>import test from 'tape';
import path from 'path';
import url from 'url';
import resolvePath from '../src/resolve-path';

test('sanity check', assert => {
  assert.equal(typeof resolvePath, 'function', 'can import resolvePath and it is a function');
  assert.end();
});

test('jspm import', assert => {
  System.config({
    baseURL: __dirname,
    defaultJSExtensions: true,
    paths: {
      'github:*': 'jspm_packages/github/*',
      'npm:*': 'jspm_packages/npm/*',
    },
    map: {
      'mock-package': 'npm:mock-package@1.0.0',
    },
  });

  const request = {
    current: 'jspm:mock-package/mock-asset',
    previous: 'stdin',
  };

  resolvePath(request, '/')
    .then(p => {
      // System.normalize in resolvePath will give us the absolute path
      const relative = '/' + path.relative(__dirname, url.parse(p).path);
      assert.equal(relative, '/jspm_packages/npm/mock-package@1.0.0/mock-asset.scss', 'resolves "jspm:" import');
    })
    .catch(e => assert.fail(e))
    .then(() => assert.end());
});

test('nested imports', assert => {
  let request = {
    current: 'mixins/mixin',
    previous: '/jspm_packages/npm/mock-package@1.0.0/mock-asset',
  };

  resolvePath(request, '/')
    .then(p => {
      assert.equal(p, '/jspm_packages/npm/mock-package@1.0.0/mixins/mixin.scss', 'resolves a nested import');
    })
    .catch(e => assert.fail(e))
    .then(() => {
      request = {
        current: 'deeper/mixin',
        previous: 'mixins/mixin',
      };

      resolvePath(request, '/')
        .then(p => {
          assert.equal(p, '/jspm_packages/npm/mock-package@1.0.0/mixins/deeper/mixin.scss', 'resolves a double nested import');
        })
        .catch(e => assert.fail(e))
        .then(() => assert.end());
    });
});
<commit_msg>Fix test that fails on Windows due to path separator differences. Use ES2015 template literals.<commit_after>import test from 'tape';
import path from 'path';
import url from 'url';
import resolvePath from '../src/resolve-path';

test('sanity check', assert => {
  assert.equal(typeof resolvePath, 'function', 'can import resolvePath and it is a function');
  assert.end();
});

test('jspm import', assert => {
  System.config({
    baseURL: __dirname,
    defaultJSExtensions: true,
    paths: {
      'github:*': 'jspm_packages/github/*',
      'npm:*': 'jspm_packages/npm/*',
    },
    map: {
      'mock-package': 'npm:mock-package@1.0.0',
    },
  });

  const request = {
    current: 'jspm:mock-package/mock-asset',
    previous: 'stdin',
  };

  resolvePath(request, '/')
    .then(p => {
      // System.normalize in resolvePath will give us the absolute path
      const relative = `/${path.relative(__dirname, url.parse(p).path)}`;
      assert.equal(relative, `/${path.join('jspm_packages', 'npm', 'mock-package@1.0.0', 'mock-asset.scss')}`);
    })
    .catch(e => assert.fail(e))
    .then(() => assert.end());
});

test('nested imports', assert => {
  let request = {
    current: 'mixins/mixin',
    previous: '/jspm_packages/npm/mock-package@1.0.0/mock-asset',
  };

  resolvePath(request, '/')
    .then(p => {
      assert.equal(p, '/jspm_packages/npm/mock-package@1.0.0/mixins/mixin.scss', 'resolves a nested import');
    })
    .catch(e => assert.fail(e))
    .then(() => {
      request = {
        current: 'deeper/mixin',
        previous: 'mixins/mixin',
      };

      resolvePath(request, '/')
        .then(p => {
          assert.equal(p, '/jspm_packages/npm/mock-package@1.0.0/mixins/deeper/mixin.scss', 'resolves a double nested import');
        })
        .catch(e => assert.fail(e))
        .then(() => assert.end());
    });
});
<|endoftext|>


<commit_before>var express = require('express');
var ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn;
var db = require('../db');

var router = express.Router();

/* GET users listing. */
router.get('/', ensureLoggedIn(), function(req, res, next) {
  db.get('SELECT rowid AS id, username, name FROM users WHERE rowid = ?', [ req.user.id ], function(err, row) {
    if (err) { return next(err); }

    // TODO: Handle undefined row.

    var user = {
      id: row.id.toString(),
      username: row.username,
      displayName: row.name
    };
    res.render('profile', { user: user });
  });
});

module.exports = router;
<commit_msg>Clean up myaccount route.<commit_after>var express = require('express');
var ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn;
var db = require('../db');

var router = express.Router();

/* GET users listing. */
router.get('/',
  ensureLoggedIn(),
  function(req, res, next) {
    db.get('SELECT rowid AS id, username, name FROM users WHERE rowid = ?', [ req.user.id ], function(err, row) {
      if (err) { return next(err); }

      // TODO: Handle undefined row.

      var user = {
        id: row.id.toString(),
        username: row.username,
        displayName: row.name
      };
      res.render('profile', { user: user });
    });
  });

module.exports = router;
<|endoftext|>


<commit_before>const _ = require('lodash');
const moment = require('moment');
const Promise = require('bluebird');
const debug = require('debug')('routes:semantics');
const nconf = require('nconf');

const mongo = require('../lib/mongo');
const params = require('../lib/params');

const supported = [ "it", "pl", "en", "fr", "es", "pt", "ru", "he", "nl", "de", "et", "hr",
                    "ro", "uk", "fa", "fi", "hu", "no", "da", "ja", "lt", "id", "sv", "cs",
                    "lv", "ar", "af", "ko", "sk", "el", "th", "tr", "bn", "bg", "sl", "hi", "tl" ];

const LanguageError = {
    error: true,
    message: 'missing language',
    supported,
    reminder: 'the list of supported language comes from a mongodb.distinct call, should be updated'
};
function validLanguage(propl) {
    return (_.size(propl) == 2 || supported.indexOf(propl) !== -1);
}

function labels(req) {
    const { amount, skip } = params.optionParsing(req.params.paging, 100);
    debug("labels request (lang: %s), amount %d skip %d",
        req.params.lang, amount, skip);

    if(!validLanguage(req.params.lang))
        return { json: LanguageError };

    return mongo
        .readLimit(nconf.get('schema').labels, { lang: req.params.lang }, { when: -1 }, amount, skip)
        .then(function(data) {
            debug("retrived %d objects, with amount %d skip %d", _.size(data), amount, skip);
            return { json: data };
        })
        .catch(function(e) {
            debug("data (error): %s", e);
            return { 'text': `error: ${e}` };
        });
};

function semantics(req) {
    const { amount, skip } = params.optionParsing(req.params.paging, 100);
    debug("semantic request (lang: %s), amount %d skip %d",
        req.params.lang, amount, skip);

    if(!validLanguage(req.params.lang))
        return { json: LanguageError };

    return mongo
        .readLimit(nconf.get('schema').semantics, { lang: req.params.lang }, { when: -1 }, amount, skip)
        .then(function(data) {
            debug("retrived %d objects, with amount %d skip %d", _.size(data), amount, skip);
            return { json: data };
        })
        .catch(function(e) {
            debug("data (error): %s", e);
            return { 'text': `error: ${e}` };
        });
};

function redactEnriched(enrich) {
    _.unset(enrich, '_id');
    let s = _.first(enrich.summary);
    _.unset(s, '_id');
    _.unset(s, 'id');
    _.unset(s, 'user');
    _.unset(s, 'timeline');
    _.unset(s, 'impressionOrder');
    _.unset(s, 'impressionTime');
    enrich.summary = s;
    // XXX maybe can be redacted the condition of the ❌ below
    return enrich;
}

function enrich(req) {
    const DEFAULTAMOUNT = 13;
    const backintime = moment().subtract(2, 'd').toISOString();
    const { amount, skip } = params.optionParsing(req.params.paging, DEFAULTAMOUNT);
    debug("enrich request (lang: %s), amount %d skip %d",
        req.params.lang, amount, skip);

    if(!validLanguage(req.params.lang))
        return { json: LanguageError };

    return mongo
        .aggregate(nconf.get('schema').labels, [
            { $sort: { when: -1 }},
            { $match: { lang: req.params.lang, when: { "$lt": new Date(backintime) }} },
            { $skip: skip },
            { $limit: amount },
            { $lookup: { from: 'summary', localField: 'semanticId', foreignField: 'semanticId', as: 'summary' }}
        ])
        .map(redactEnriched)
        .then(function(enriched) {
            debug("return enrich, %d objects (%j)", _.size(enriched), _.map(enriched, function(e) {
                if(!e.summary || !e.summary.source || !e.summary.nature)
                    return "❌";
                return [ e.summary.source, e.summary.nature ];
            }));
            return { json: enriched };
        });
};

function noogle(req) {
    const DEFAULTAMOUNT = 13;
    const { amount, skip } = params.optionParsing(req.params.paging, DEFAULTAMOUNT);
    debug("noogle request (lang: %s, label: %s), amount %d skip %d",
        req.params.lang, req.params.label, amount, skip);

    if(!validLanguage(req.params.lang))
        return { json: LanguageError };

    return mongo
        .aggregate(nconf.get('schema').semantics, [
            { $sort: { when: -1 }},
            { $match: { lang: req.params.lang, label: req.params.label }},
            { $group: { _id: "$semanticId" }},
            { $skip: skip },
            { $limit: amount },
            { $lookup: { from: 'summary', localField: '_id', foreignField: 'semanticId', as: 'summary' }},
            { $lookup: { from: 'labels', localField: '_id', foreignField: 'semanticId', as: 'labels' }}
        ])
        .map(function(dirty) {
            let base = _.first(dirty.labels);
            base.summary = dirty.summary;
            base.when = moment(base.when);
            return base;
        })
        .map(redactEnriched)
        .then(function(results) {
            debug("return noogle, %d objects (%j)", _.size(results), _.map(results, function(e) {
                if(!e.summary || !e.summary.source || !e.summary.nature)
                    return "❌";
                return [ e.summary.source, e.summary.nature ];
            }));
            const ordered = _.orderBy(results, {when: -1 });
            return { json: results };
        });
};

function loud(req) {
    const MAXENTRIES = 2000;
    const DEFAULTAMOUNT = 13;
    const backintime = moment().subtract(2, 'd').toISOString();
    const { amount, skip } = params.optionParsing(req.params.paging, DEFAULTAMOUNT);
    debug("loud request (lang: %s), amount %d skip %d",
        req.params.lang, amount, skip);

    if(!validLanguage(req.params.lang))
        return { json: LanguageError };

    return loudKeywordsPipeline(MAXENTRIES, backintime, amount, skip, req.params.lang)
        .then(function(loudness) {
            debug("return loudness: %j", _.map(loudness, 'label'));
            return { json: loudness };
        });

};

function loudKeywordsPipeline(maxentries, backintime, amount, skip, lang) {
    return mongo
        .aggregate(nconf.get('schema').semantics, [
            { $match: { lang: lang, when: { "$lt": new Date(backintime) }} },
            { $limit: maxentries },
            { $group: { _id: '$title', wp: { $first: '$wp'}, count: { $sum: 1 }}},
            { $sort: { "count": -1 }},
            { $skip: skip },
            { $limit: amount },
            { $project: { "label": "$_id", _id: false, wp: true, count: true }}
        ]);
}

const cache = _.reduce(supported, function(memo, l) {
    _.set(memo, l, {
        content: null,
        computedAt: null,
        next: null,
    });
    return memo;
}, {} );

function formatReturn(lang, updated) {
    if(updated) {
        cache[lang].content = updated.content;
        cache[lang].computedAt = updated.computedAt;
        cache[lang].next = updated.next
    }
    return {
        json: {
            content: cache[lang].content,
            computedt: cache[lang].computedAt.toISOString(),
            next: cache[lang].next.toISOString(),
        }
    };
};

function langinfo(req) {
    /* this API returns an complete information on a language population, with number of
     * active contributors in the last 48 hours and total keyword seen in the last 48 hours,
     * this API do not support paging and is meant to be cached */
    const MAXENTRIES = 60000;
    const DEFAULTAMOUNT = 10;
    const hoursNumber = 48;
    const backintime = moment().subtract(hoursNumber, 'h').toISOString();
    const amount = 13;
    const skip = 0;
    const lang = req.params.lang;
    const cacheMinutes = 60 * 2;

    if(!validLanguage(lang))
        return { json: LanguageError };

    if(cache[lang].next && moment().isBefore(cache[lang].next) && cache[lang].content) {
        debug("langInfo %s, using cached version", lang);
        return formatReturn(lang);
    }

    debug("langInfo %s, retrieving from DB", lang);
    return Promise.all([
        loudKeywordsPipeline(MAXENTRIES, backintime, amount, skip, lang),
        mongo.aggregate(nconf.get('schema').semantics, [
            { $sort: { when: -1 }},
            { $match: { lang: req.params.lang, when: { "$gt": new Date(backintime) } }},
            { $limit: MAXENTRIES },
            { $group: { _id: "$label" }},
            { $group: { _id: null, amount: { "$sum": 1 } }},
        ]).then(function(o) { return (o && o[0] && o[0].amount) ? obj[0].amount : 0 }),
        mongo.aggregate(nconf.get('schema').labels, [
            { $sort: { when: -1 }},
            { $match: { lang: lang, when: { "$gt": new Date(backintime) } }},
            { $limit: MAXENTRIES },
            { $lookup: { from: 'summary', localField: 'semanticId', foreignField: 'semanticId', as: 'summary' }},
            { $group: { _id: "dummy", profiles: { $addToSet: "$summary.user"} }}
        ]).then(function(usermess) {
            // remind/TODO grouped by size and anonymized, this can be a nice stats
            // _.flatten(usermess[0].profiles)
            if(!(usermess && usermess[0] && usermess[0].profiles))
                return 0;
            return _.size(_.uniq(_.flatten(usermess[0].profiles)))
        })
    ])
    .then(function(results) {
        let retval = {
            consideredHoursWindow: hoursNumber,
            language: req.params.lang,
            most: _.map(results[0], 'label'),
            labelsCount: results[1],
            contributors: results[2],
        };
        return formatReturn(lang, {
            content: retval,
            computedAt: moment(),
            next: moment().add(cacheMinutes, 'minutes')
        });
    });
}

module.exports = {
    labels,
    semantics,
    enrich,
    loud,
    noogle,
    langinfo
};
<commit_msg>shame on me, committed a bug<commit_after>const _ = require('lodash');
const moment = require('moment');
const Promise = require('bluebird');
const debug = require('debug')('routes:semantics');
const nconf = require('nconf');

const mongo = require('../lib/mongo');
const params = require('../lib/params');

const supported = [ "it", "pl", "en", "fr", "es", "pt", "ru", "he", "nl", "de", "et", "hr",
                    "ro", "uk", "fa", "fi", "hu", "no", "da", "ja", "lt", "id", "sv", "cs",
                    "lv", "ar", "af", "ko", "sk", "el", "th", "tr", "bn", "bg", "sl", "hi", "tl" ];

const LanguageError = {
    error: true,
    message: 'missing language',
    supported,
    reminder: 'the list of supported language comes from a mongodb.distinct call, should be updated'
};
function validLanguage(propl) {
    return (_.size(propl) == 2 || supported.indexOf(propl) !== -1);
}

function labels(req) {
    const { amount, skip } = params.optionParsing(req.params.paging, 100);
    debug("labels request (lang: %s), amount %d skip %d",
        req.params.lang, amount, skip);

    if(!validLanguage(req.params.lang))
        return { json: LanguageError };

    return mongo
        .readLimit(nconf.get('schema').labels, { lang: req.params.lang }, { when: -1 }, amount, skip)
        .then(function(data) {
            debug("retrived %d objects, with amount %d skip %d", _.size(data), amount, skip);
            return { json: data };
        })
        .catch(function(e) {
            debug("data (error): %s", e);
            return { 'text': `error: ${e}` };
        });
};

function semantics(req) {
    const { amount, skip } = params.optionParsing(req.params.paging, 100);
    debug("semantic request (lang: %s), amount %d skip %d",
        req.params.lang, amount, skip);

    if(!validLanguage(req.params.lang))
        return { json: LanguageError };

    return mongo
        .readLimit(nconf.get('schema').semantics, { lang: req.params.lang }, { when: -1 }, amount, skip)
        .then(function(data) {
            debug("retrived %d objects, with amount %d skip %d", _.size(data), amount, skip);
            return { json: data };
        })
        .catch(function(e) {
            debug("data (error): %s", e);
            return { 'text': `error: ${e}` };
        });
};

function redactEnriched(enrich) {
    _.unset(enrich, '_id');
    let s = _.first(enrich.summary);
    _.unset(s, '_id');
    _.unset(s, 'id');
    _.unset(s, 'user');
    _.unset(s, 'timeline');
    _.unset(s, 'impressionOrder');
    _.unset(s, 'impressionTime');
    enrich.summary = s;
    // XXX maybe can be redacted the condition of the ❌ below
    return enrich;
}

function enrich(req) {
    const DEFAULTAMOUNT = 13;
    const backintime = moment().subtract(2, 'd').toISOString();
    const { amount, skip } = params.optionParsing(req.params.paging, DEFAULTAMOUNT);
    debug("enrich request (lang: %s), amount %d skip %d",
        req.params.lang, amount, skip);

    if(!validLanguage(req.params.lang))
        return { json: LanguageError };

    return mongo
        .aggregate(nconf.get('schema').labels, [
            { $sort: { when: -1 }},
            { $match: { lang: req.params.lang, when: { "$lt": new Date(backintime) }} },
            { $skip: skip },
            { $limit: amount },
            { $lookup: { from: 'summary', localField: 'semanticId', foreignField: 'semanticId', as: 'summary' }}
        ])
        .map(redactEnriched)
        .then(function(enriched) {
            debug("return enrich, %d objects (%j)", _.size(enriched), _.map(enriched, function(e) {
                if(!e.summary || !e.summary.source || !e.summary.nature)
                    return "❌";
                return [ e.summary.source, e.summary.nature ];
            }));
            return { json: enriched };
        });
};

function noogle(req) {
    const DEFAULTAMOUNT = 13;
    const { amount, skip } = params.optionParsing(req.params.paging, DEFAULTAMOUNT);
    debug("noogle request (lang: %s, label: %s), amount %d skip %d",
        req.params.lang, req.params.label, amount, skip);

    if(!validLanguage(req.params.lang))
        return { json: LanguageError };

    return mongo
        .aggregate(nconf.get('schema').semantics, [
            { $sort: { when: -1 }},
            { $match: { lang: req.params.lang, label: req.params.label }},
            { $group: { _id: "$semanticId" }},
            { $skip: skip },
            { $limit: amount },
            { $lookup: { from: 'summary', localField: '_id', foreignField: 'semanticId', as: 'summary' }},
            { $lookup: { from: 'labels', localField: '_id', foreignField: 'semanticId', as: 'labels' }}
        ])
        .map(function(dirty) {
            let base = _.first(dirty.labels);
            base.summary = dirty.summary;
            base.when = moment(base.when);
            return base;
        })
        .map(redactEnriched)
        .then(function(results) {
            debug("return noogle, %d objects (%j)", _.size(results), _.map(results, function(e) {
                if(!e.summary || !e.summary.source || !e.summary.nature)
                    return "❌";
                return [ e.summary.source, e.summary.nature ];
            }));
            const ordered = _.orderBy(results, {when: -1 });
            return { json: results };
        });
};

function loud(req) {
    const MAXENTRIES = 2000;
    const DEFAULTAMOUNT = 13;
    const backintime = moment().subtract(2, 'd').toISOString();
    const { amount, skip } = params.optionParsing(req.params.paging, DEFAULTAMOUNT);
    debug("loud request (lang: %s), amount %d skip %d",
        req.params.lang, amount, skip);

    if(!validLanguage(req.params.lang))
        return { json: LanguageError };

    return loudKeywordsPipeline(MAXENTRIES, backintime, amount, skip, req.params.lang)
        .then(function(loudness) {
            debug("return loudness: %j", _.map(loudness, 'label'));
            return { json: loudness };
        });

};

function loudKeywordsPipeline(maxentries, backintime, amount, skip, lang) {
    return mongo
        .aggregate(nconf.get('schema').semantics, [
            { $match: { lang: lang, when: { "$lt": new Date(backintime) }} },
            { $limit: maxentries },
            { $group: { _id: '$title', wp: { $first: '$wp'}, count: { $sum: 1 }}},
            { $sort: { "count": -1 }},
            { $skip: skip },
            { $limit: amount },
            { $project: { "label": "$_id", _id: false, wp: true, count: true }}
        ]);
}

const cache = _.reduce(supported, function(memo, l) {
    _.set(memo, l, {
        content: null,
        computedAt: null,
        next: null,
    });
    return memo;
}, {} );

function formatReturn(lang, updated) {
    if(updated) {
        cache[lang].content = updated.content;
        cache[lang].computedAt = updated.computedAt;
        cache[lang].next = updated.next
    }
    return {
        json: {
            content: cache[lang].content,
            computedt: cache[lang].computedAt.toISOString(),
            next: cache[lang].next.toISOString(),
        }
    };
};

function langinfo(req) {
    /* this API returns an complete information on a language population, with number of
     * active contributors in the last 48 hours and total keyword seen in the last 48 hours,
     * this API do not support paging and is meant to be cached */
    const MAXENTRIES = 60000;
    const DEFAULTAMOUNT = 10;
    const hoursNumber = 48;
    const backintime = moment().subtract(hoursNumber, 'h').toISOString();
    const amount = 13;
    const skip = 0;
    const lang = req.params.lang;
    const cacheMinutes = 60 * 2;

    if(!validLanguage(lang))
        return { json: LanguageError };

    if(cache[lang].next && moment().isBefore(cache[lang].next) && cache[lang].content) {
        debug("langInfo %s, using cached version", lang);
        return formatReturn(lang);
    }

    debug("langInfo %s, retrieving from DB", lang);
    return Promise.all([
        loudKeywordsPipeline(MAXENTRIES, backintime, amount, skip, lang),
        mongo.aggregate(nconf.get('schema').semantics, [
            { $sort: { when: -1 }},
            { $match: { lang: req.params.lang, when: { "$gt": new Date(backintime) } }},
            { $limit: MAXENTRIES },
            { $group: { _id: "$label" }},
            { $group: { _id: null, amount: { "$sum": 1 } }},
        ]).then(function(o) { return (o && o[0] && o[0].amount) ? o[0].amount : 0 }),
        mongo.aggregate(nconf.get('schema').labels, [
            { $sort: { when: -1 }},
            { $match: { lang: lang, when: { "$gt": new Date(backintime) } }},
            { $limit: MAXENTRIES },
            { $lookup: { from: 'summary', localField: 'semanticId', foreignField: 'semanticId', as: 'summary' }},
            { $group: { _id: "dummy", profiles: { $addToSet: "$summary.user"} }}
        ]).then(function(usermess) {
            // remind/TODO grouped by size and anonymized, this can be a nice stats
            // _.flatten(usermess[0].profiles)
            if(!(usermess && usermess[0] && usermess[0].profiles))
                return 0;
            return _.size(_.uniq(_.flatten(usermess[0].profiles)))
        })
    ])
    .then(function(results) {
        let retval = {
            consideredHoursWindow: hoursNumber,
            language: req.params.lang,
            most: _.map(results[0], 'label'),
            labelsCount: results[1],
            contributors: results[2],
        };
        return formatReturn(lang, {
            content: retval,
            computedAt: moment(),
            next: moment().add(cacheMinutes, 'minutes')
        });
    });
}

module.exports = {
    labels,
    semantics,
    enrich,
    loud,
    noogle,
    langinfo
};
<|endoftext|>