<commit_before>'use strict';

/*
* When adding modules to the namespace, ensure that they are added in alphabetical order according to module name.
*/

// MODULES //

var setReadOnly = require( '@stdlib/utils/define-read-only-property' );

// MAIN //

/**
* Top-level namespace.
*
* @namespace ns
*/
var ns = {};

/**
* @name daysInYear
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/time/days-in-year}
*/
setReadOnly( ns, 'daysInYear', require( '@stdlib/time/days-in-year' ) );

/**
* @name isoWeeksInYear
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/time/iso-weeks-in-year}
*/
setReadOnly( ns, 'isoWeeksInYear', require( '@stdlib/time/iso-weeks-in-year' ) );

/**
* @name now
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/time/now}
*/
setReadOnly( ns, 'now', require( '@stdlib/time/now' ) );

/**
* @name tic
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/time/tic}
*/
setReadOnly( ns, 'tic', require( '@stdlib/time/tic' ) );

/**
* @name toc
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/time/toc}
*/
setReadOnly( ns, 'toc', require( '@stdlib/time/toc' ) );

// EXPORTS //

module.exports = ns;
<commit_msg>Update namespace<commit_after>'use strict';

/*
* When adding modules to the namespace, ensure that they are added in alphabetical order according to module name.
*/

// MODULES //

var setReadOnly = require( '@stdlib/utils/define-read-only-property' );

// MAIN //

/**
* Top-level namespace.
*
* @namespace ns
*/
var ns = {};

/**
* @name daysInMonth
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/time/days-in-month}
*/
setReadOnly( ns, 'daysInMonth', require( '@stdlib/time/days-in-month' ) );

/**
* @name daysInYear
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/time/days-in-year}
*/
setReadOnly( ns, 'daysInYear', require( '@stdlib/time/days-in-year' ) );

/**
* @name isoWeeksInYear
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/time/iso-weeks-in-year}
*/
setReadOnly( ns, 'isoWeeksInYear', require( '@stdlib/time/iso-weeks-in-year' ) );

/**
* @name now
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/time/now}
*/
setReadOnly( ns, 'now', require( '@stdlib/time/now' ) );

/**
* @name tic
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/time/tic}
*/
setReadOnly( ns, 'tic', require( '@stdlib/time/tic' ) );

/**
* @name toc
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/time/toc}
*/
setReadOnly( ns, 'toc', require( '@stdlib/time/toc' ) );

// EXPORTS //

module.exports = ns;
<|endoftext|>


<commit_before>/**
 * Reordering utilities for the items drag and drop ordering page -AZ
 * Does not put stuff into the global namespace and uses only methods that are compatible with most browsers
 *
 * Update: Functions have been exposed to enable startSort() to be accessible by custom plugins - lovemore.nalube@uct.ac.za
 **/
var sortableIds = new Array();

$(document).ready(
        function () {
            startSort();
            $(document).trigger('activateControls.templateItems');
            buildSortableIds();
        }
    );

function $it(id) {
    return document.getElementById(id);
}

function enableOrderButtons() {
    try {
        $("#revertOrderButton")
                .attr('disabled', false)
                // put original order into the revertOrder trigger
                .bind('click', function() {
                    disableOrderButtons();
                    for (var i = sortableIds.length - 1; i > 0; --i) {
                        var thisitem = $it(sortableIds[i]);
                        var previtem = $it(sortableIds[i - 1]);
                        previtem.parentNode.removeChild(previtem);
                        thisitem.parentNode.insertBefore(previtem, thisitem);
                        setIndex(thisitem.id, i);
                    }
                    setIndex(sortableIds[0], 0);
                });
        $("#saveReorderButton").attr('disabled', false);
        $("#orderInputs").attr("class","itemOperationsEnabled");
    }catch(e){}
}

function disableOrderButtons() {
    try {
        $("#revertOrderButton").attr('disabled', true);
        $("#saveReorderButton").attr('disabled', true);
        $("#orderInputs").attr("class","itemOperations");
    }catch(e){}
}

function buildSortableIds() {
    $("#itemList > div.itemRow").each(function(){
       sortableIds.push($(this).attr('id'));
    });
}

function setIndex(itemId, newindex) {
    if (newindex != null || newindex != "") {
        var changed = sortableIds[newindex] != itemId;
        var itemSelect = document.getElementById(itemId + "item-select-selection");
        itemSelect.selectedIndex = newindex;
        // NOTE: might need to disable the pulldowns once the user drags
        $it(itemId + "hidden-item-num").value = newindex;
    }
}

function startSort() {
    $("#itemList").sortable({
        axis:         'y',
        cancel:     ':input, button, a, select, div.itemLine3',
        connectWith: ['.itemTableBlock'],
        cursor:     'move',
        delay:         '1',
        opacity:     '0.9',
        scroll:     true,
        update: function(data) {
            var list = $("#itemList > div").get();
            for (var i = 0; i < list.length - 1; i++) {
                if (list[i].id) {
                    setIndex(list[i].id, i);
                }
            }
            enableOrderButtons();
        }

    });
    $(".itemTableBlock").each(function() {
        $(this).sortable({
            axis: 'y',
            cancel: ':input, button, a, select',
            containment: 'parent',
            cursor: 'move',
            over: function(e, ui) {
                if ($('#closeItemOperationsEnabled').length > 0) {
                    $('#closeItemOperationsEnabled').parent().remove();
                }
            },
            deactivate: function(e, ui) {
                if ((ui.sender.attr('id') != "itemTableBlock") && (ui.item.parent().attr('id') != "itemList")) {
                    var that = ui.item.find('.itemCheckbox > input').eq(0).attr('id');
                    if (that == null) {
                        $(document).trigger('block.rejectItem', [ui , "plain"]);
                    }
                    var target = ui.item.parents('.itemRow').eq(0).find('.itemCheckbox > input').eq(0).attr('id');
                    if (target == null)
                        return false;
                    var targetVal = target.substring(target.indexOf('-') + 1, target.lastIndexOf('-'));
                    var thatVal = that.substring(that.indexOf('-') + 1, that.lastIndexOf('-'));
                    if (targetVal != thatVal) {
                        $(document).trigger('block.rejectItem', [ui]);
                    } else {

                        var confirmMsg = '\

Are you sure?

\

 \
                              <input type="button" name="blockChildConfirm" msg="yes" accesskey="s" value="Yes"/>  |  \
                              Cancel  \

 \
                                         ';

                        $.facebox(confirmMsg);

                        $('#facebox .header').eq(0).hide();
                        $('#facebox table').attr('width', 250);
                        $('#facebox .body').css('width', 250);
                        $('#facebox .body [name*=blockChildConfirm]').click(function() {
                            if ($(this).attr('msg') == 'yes') {
                                $.facebox('\

  Saving... \

 \

 \
                               Please do not close this window.  \

 ');
                                ui.item.attr('style', 'display:none');
                                var shadow = ui.item.parent().find('.itemRowBlock').eq(0).clone(true);
                                shadow.insertAfter(ui.item);
                                ui.item.parent().find('div[id="' + shadow.attr('id') + '"]').not(':lt(2)').remove();
                                shadow.find('span').eq(1).html(ui.item.find('.itemText > span').eq(0).html());
                                shadow.find('input').eq(0).val(ui.item.find('a[templateitemid]').attr('templateitemid'));
                                shadow.find('a[templateitemid]').attr('templateitemid', ui.item.find('a[templateitemid]').attr('templateitemid'));
                                shadow.find('a[otp]').attr('otp', ui.item.find('a[otp]').attr('otp'));
                                $(document).trigger('block.saveReorder', [ui]);
                                return false;

                            }
                            else if ($(this).attr('msg') == 'no') {
                                $(document).trigger('block.rejectItem', [ui, "noAlert"]);
                                $(document).trigger('close.facebox');
                                return false;
                            }

                        });
                    }

                }
                return false;
            },
            delay: '1',
            items: 'div',
            opacity: '0.9',
            scroll: true,
            update: function(e, ui) {
                $(document).trigger('block.triggerChildrenSort', [ui]);
                if (ui.item.parents('.itemTableBlock').find('.itemBlockSave').length == 0) {
                    var saveAction = 'Save new order for grouped items';
                    $(saveAction).appendTo(ui.item.parents('.itemTableBlock').children('.instruction').eq(0));
                    ui.item.parents('.itemTableBlock').children('.instruction').eq(0).effect('highlight', 1500);
                    ui.item.parents('.itemTableBlock').find('.itemBlockSave').bind('click', function() {
                        var order = [];
                        ui.item.parents('.itemTableBlock').find('div.itemRowBlock').not('.ui-sortable-helper').each(function(){
                            order.push($(this).find('a[templateitemid]').attr('templateitemid'));
                        });
                        var templateItemId = ui.item.parents('.itemRow').children('.itemLine2').find('a[templateitemid]').attr('templateitemid'),
                        params = {
                            templateItemId : templateItemId,
                            orderedIds : order.toString()
                        },
                        fnBefore = function(){
                            $(document).trigger('block.triggerChildrenSort', [ui]);
                            ui.item.parents('.itemTableBlock').sortable('disable');
                        },
                        fnAfter = function(){
                            ui.item.parents('.itemRow').find('.itemBlockSave').fadeOut(0, function() {
                                $(this).remove();
                            });
                            $(document).trigger('activateControls.templateItems');
                            ui.item.parents('.itemTableBlock').sortable('enable');
                        };
                        evalTemplateData.item.saveBlockOrder(params, fnBefore, fnAfter);
                        return false;

                    });
                }

            }
        });
    });
}
function refreshSort() {
    $("div.itemList").sortable('destroy');
    $("div.itemTableBlock").sortable('destroy');
    startSort();
}
$(document).bind('block.triggerChildrenSort', function(e, ui) {
    var item = ui.item? ui.item : ui;

    var count = 1;
    item.parents('.itemTableBlock').find('div.itemRowBlock').not('.ui-sortable-helper').each(function(){
        $(this).find('.itemLabel').text(count);
        count++;
    });
});
$(document).bind('block.rejectItem', function(e, ui, option) {
    $(document).trigger('list.warning', [ui, option, 'block', 'Sorry this item cannot be grouped here. It is not the same type as the grouped items.']);
    var shadow = ui.item.clone(true);
    var shadowPlace = $('.itemList > div.itemRow').eq(ui.item.find('input').eq(0).val()).attr('id');
    ui.item.remove();
    shadow.insertBefore($('[id="' + shadowPlace + '"]'));
    shadow.removeAttr('style').effect('highlight', 3500);
    $('.itemList div[id="' + shadow.attr('id') + '"]').not(':lt(1)').remove();
    refreshSort();
    var list = $(".itemList > div.itemRow").get();
    for (var i = 0; i < list.length; i++) {
        if (list[i].id) {
            setIndex(list[i].id, i);
        }
    }
});

$(document).bind('block.saveReorder', function(e, ui, type) {
    var item = ui.item? ui.item : ui;
    ui = {item: item};

    $(document).trigger('list.busy', true);
    var currentOrder = item.parents('.itemRow').children('input[name=*hidden-item-num]').eq(0).val();
    $.get('/direct/eval-templateitem/' + item.parents('.itemRow').children('.itemLine2').find('a[templateitemid]').attr('templateitemid') + '.xml',
            function(msg) {
                var itemText = $(msg).find('item > itemText').text();
                var usesNA = $(msg).find('usesNA').eq(1).text();
                var category = $(msg).find('category').eq(1).text();
                var idealColour =  $(msg).find('item > scaleDisplaySetting').text();
                var t = item.parents('.itemRow').eq(0).children('.itemLine2').find('a[templateitemid]');

                $.ajax({
                    url: "remove_item",
                    data: 'templateItemId=' + t.attr('templateitemid') + '&templateId=' + t.attr('templateid') + '&command+link+parameters%26deletion-binding%3Dl%2523%257B' + t.attr('otp') + '%257D%26Submitting%2520control%3Dremove-item-command-link=Remove+Item',
                    type: "POST",
                    success: function() {
                        var params = 'item-text=' + itemText;
                        params += '&item-text-fossil=istring%23%7BtemplateItemWBL.new1.item.itemText%7D';
                        params += '&showItemCategory%3A%3Aitem-category-list-selection-fossil=istring%23%7BtemplateItemWBL.new1.category%7DCourse';
                        params += '$showItemCategory%3A%3Aitem-category-list-selection=' + category;
                        params += '&idealColor-fossil=iboolean%23%7BtemplateBBean.idealColor%7Dfalse';
                        params += '&idealColor=' + (idealColour == "Stepped" ? "false" : "true");
                        params += '&item_NA-fossil=iboolean%23%7BtemplateItemWBL.new1.usesNA%7Dfalse';
                        params += '&item_NA=' + usesNA;
                        var ordering = new Array();
                        item.parents('.itemTableBlock').eq(0).children('.itemRowBlock').filter(':visible').each(function() {
                            var val = $(this).children('input[name=hidden-item-id]').eq(0).val();
                            params += '&hidden-item-id=' + val;
                            ordering.push(val);
                        });
                        params += '&templateItemIds=' + ordering.toString();
                        params += '&ordered-child-ids=' + ordering.toString();
                        params += '&ordered-child-ids-fossil=istring%23%7BtemplateBBean.orderedChildIds%7D';
                        params += '&templateId=' + t.attr('templateid');
                        params += '&command+link+parameters%26el-binding%3Dj%2523%257BtemplateBBean.blockId%257Dnew1%26el-binding%3Dj%2523%257BtemplateBBean.templateItemIds%257D' + ordering.toString() + '%26el-binding%3Dj%2523%257BtemplateBBean.originalDisplayOrder%257D2%26Submitting%2520control%3DsaveBlockAction%26Fast%2520track%2520action%3DtemplateBBean.saveBlockItemAction=Save+item';
                        $.ajax({
                            url: "modify_template",
                            data: params,
                            type: "POST",
                            success: function(d) {
                                var id = 'input[value=' + item.find('input[name=hidden-item-id]').eq(0).val() + ']:hidden';
                                item.parents('.itemRow').eq(0).html($(d).find(id).parents('.itemRow').eq(0).html());
                                $(document).trigger('list.triggerSort');
                                item.parents('.itemRow').eq(0).children('input[name=*hidden-item-num]').eq(0).val(currentOrder);

                                $('form[name=modify_form_rows]').ajaxSubmit({
                                    success: function(d) {
                                        $('#itemList').html($(d).find('#itemList').html());
                                        $(document).trigger('list.triggerSort', [ui]);
                                        $(document).trigger('activateControls.templateItems');
                                        $(document).trigger('close.facebox');
                                        item.parents('.itemTableBlock').effect('highlight', 3500);
                                        $(document).trigger('list.busy', false);
                                    }
                                });
                            }
                        });
                    }
                });
    });
});
$(document).bind('list.triggerSort', function() {
var c = 0;
$(".itemList > div.itemRow").filter(':visible').each(function() {
    $(this).find('select').eq(0).val(c + 1);
    $(this).find('input[name=*hidden-item-num]').eq(0).val(c);
    c++;
});
});

$(document).bind('list.warning', function(e, ui, option, extra, err){
    var item = ui.item? ui.item : ui;
        if (option != "noAlert") {
        $('.itemOperationsEnabled').remove();
        var error = '
\
                        \
                        '+ err +' close
\
                        ';
        if(extra == 'block')
            item.parents('.itemLine3').prepend(error);
        $('#closeItemOperationsEnabled').click(function() {
            $(this).parent().slideUp('normal', function() {
                $(this).remove()
            });
            return false
        });
    }
});
<commit_msg>EVALSYS-784 Fix toggle group items issue as a result of r62232 commit<commit_after>/**
 * Reordering utilities for the items drag and drop ordering page -AZ
 * Does not put stuff into the global namespace and uses only methods that are compatible with most browsers
 *
 * Update: Functions have been exposed to enable startSort() to be accessible by custom plugins - lovemore.nalube@uct.ac.za
 **/
var sortableIds = new Array();

$(document).ready(
        function () {
            startSort();
            $(document).trigger('activateControls.templateItems');
            buildSortableIds();
        }
    );

function $it(id) {
    return document.getElementById(id);
}

function enableOrderButtons() {
    try {
        $("#revertOrderButton")
                .attr('disabled', false)
                // put original order into the revertOrder trigger
                .bind('click', function() {
                    disableOrderButtons();
                    for (var i = sortableIds.length - 1; i > 0; --i) {
                        var thisitem = $it(sortableIds[i]);
                        var previtem = $it(sortableIds[i - 1]);
                        previtem.parentNode.removeChild(previtem);
                        thisitem.parentNode.insertBefore(previtem, thisitem);
                        setIndex(thisitem.id, i);
                    }
                    setIndex(sortableIds[0], 0);
                });
        $("#saveReorderButton").attr('disabled', false);
        $("#orderInputs").attr("class","itemOperationsEnabled");
    }catch(e){}
}

function disableOrderButtons() {
    try {
        $("#revertOrderButton").attr('disabled', true);
        $("#saveReorderButton").attr('disabled', true);
        $("#orderInputs").attr("class","itemOperations");
    }catch(e){}
}

function buildSortableIds() {
    $("#itemList > div.itemRow").each(function(){
       sortableIds.push($(this).attr('id'));
    });
}

function setIndex(itemId, newindex) {
    if (newindex != null || newindex != "") {
        var changed = sortableIds[newindex] != itemId;
        var itemSelect = document.getElementById(itemId + "item-select-selection");
        itemSelect.selectedIndex = newindex;
        // NOTE: might need to disable the pulldowns once the user drags
        $it(itemId + "hidden-item-num").value = newindex;
    }
}

function startSort() {
    $("#itemList").sortable({
        axis:         'y',
        cancel:     ':input, button, a, select, div.itemLine3',
        connectWith: ['.itemTableBlock'],
        cursor:     'move',
        delay:         '1',
        opacity:     '0.9',
        scroll:     true,
        update: function(data) {
            var list = $("#itemList > div").get();
            for (var i = 0; i < list.length - 1; i++) {
                if (list[i].id) {
                    setIndex(list[i].id, i);
                }
            }
            enableOrderButtons();
        }

    });
    $(".itemTableBlock").each(function() {
        $(this).sortable({
            axis: 'y',
            cancel: ':input, button, a, select',
            containment: 'parent',
            cursor: 'move',
            over: function(e, ui) {
                if ($('#closeItemOperationsEnabled').length > 0) {
                    $('#closeItemOperationsEnabled').parent().remove();
                }
            },
            deactivate: function(e, ui) {
                if ((ui.sender.attr('id') != "itemTableBlock") && (ui.item.parent().attr('id') != "itemList")) {
                    var that = ui.item.find('.itemCheckbox > input').eq(0).attr('id');
                    if (that == null) {
                        $(document).trigger('block.rejectItem', [ui , "plain"]);
                    }
                    var target = ui.item.parents('.itemRow').eq(0).find('.itemCheckbox > input').eq(0).attr('id');
                    if (target == null)
                        return false;
                    var targetVal = target.substring(target.indexOf('-') + 1, target.lastIndexOf('-'));
                    var thatVal = that.substring(that.indexOf('-') + 1, that.lastIndexOf('-'));
                    if (targetVal != thatVal) {
                        $(document).trigger('block.rejectItem', [ui]);
                    } else {

                        var confirmMsg = '\

Are you sure?

\

 \
                              <input type="button" name="blockChildConfirm" msg="yes" accesskey="s" value="Yes"/>  |  \
                              Cancel  \

 \
                                         ';

                        $.facebox(confirmMsg);

                        $('#facebox .header').eq(0).hide();
                        $('#facebox table').attr('width', 250);
                        $('#facebox .body').css('width', 250);
                        $('#facebox .body [name*=blockChildConfirm]').click(function() {
                            if ($(this).attr('msg') == 'yes') {
                                $.facebox('\

  Saving... \

 \

 \
                               Please do not close this window.  \

 ');
                                ui.item.attr('style', 'display:none');
                                var shadow = ui.item.parent().find('.itemRowBlock').eq(0).clone(true);
                                shadow.insertAfter(ui.item);
                                ui.item.parent().find('div[id="' + shadow.attr('id') + '"]').not(':lt(2)').remove();
                                shadow.find('span').eq(1).html(ui.item.find('.itemText > span').eq(0).html());
                                shadow.find('input').eq(0).val(ui.item.find('a[templateitemid]').attr('templateitemid'));
                                shadow.find('a[templateitemid]').attr('templateitemid', ui.item.find('a[templateitemid]').attr('templateitemid'));
                                shadow.find('a[otp]').attr('otp', ui.item.find('a[otp]').attr('otp'));
                                $(document).trigger('block.saveReorder', [ui]);
                                return false;

                            }
                            else if ($(this).attr('msg') == 'no') {
                                $(document).trigger('block.rejectItem', [ui, "noAlert"]);
                                $(document).trigger('close.facebox');
                                return false;
                            }

                        });
                    }

                }
                return false;
            },
            delay: '1',
            items: 'div',
            opacity: '0.9',
            scroll: true,
            update: function(e, ui) {
                $(document).trigger('block.triggerChildrenSort', [ui]);
                if (ui.item.parents('.itemTableBlock').find('.itemBlockSave').length == 0) {
                    var saveAction = 'Save new order for grouped items';
                    $(saveAction).appendTo(ui.item.parents('.itemTableBlock').children('.instruction').eq(0));
                    ui.item.parents('.itemTableBlock').children('.instruction').eq(0).effect('highlight', 1500);
                    ui.item.parents('.itemTableBlock').find('.itemBlockSave').bind('click', function() {
                        var order = [];
                        ui.item.parents('.itemTableBlock').find('div.itemRowBlock').not('.ui-sortable-helper').each(function(){
                            order.push($(this).find('a[templateitemid]').attr('templateitemid'));
                        });
                        var templateItemId = ui.item.parents('.itemRow').children('.itemLine2').find('a[templateitemid]').attr('templateitemid'),
                        params = {
                            templateItemId : templateItemId,
                            orderedIds : order.toString()
                        },
                        fnBefore = function(){
                            $(document).trigger('block.triggerChildrenSort', [ui]);
                            ui.item.parents('.itemTableBlock').sortable('disable');
                        },
                        fnAfter = function(){
                            ui.item.parents('.itemTableBlock').sortable('enable');
                            ui.item.parents('.itemRow').find('.itemBlockSave').fadeOut(0, function() {
                                $(this).remove();
                            });
                        };
                        evalTemplateData.item.saveBlockOrder(params, fnBefore, fnAfter);
                        return false;

                    });
                }

            }
        });
    });
}
function refreshSort() {
    $("div.itemList").sortable('destroy');
    $("div.itemTableBlock").sortable('destroy');
    startSort();
}
$(document).bind('block.triggerChildrenSort', function(e, ui) {
    var item = ui.item? ui.item : ui;

    var count = 1;
    item.parents('.itemTableBlock').find('div.itemRowBlock').not('.ui-sortable-helper').each(function(){
        $(this).find('.itemLabel').text(count);
        count++;
    });
});
$(document).bind('block.rejectItem', function(e, ui, option) {
    $(document).trigger('list.warning', [ui, option, 'block', 'Sorry this item cannot be grouped here. It is not the same type as the grouped items.']);
    var shadow = ui.item.clone(true);
    var shadowPlace = $('.itemList > div.itemRow').eq(ui.item.find('input').eq(0).val()).attr('id');
    ui.item.remove();
    shadow.insertBefore($('[id="' + shadowPlace + '"]'));
    shadow.removeAttr('style').effect('highlight', 3500);
    $('.itemList div[id="' + shadow.attr('id') + '"]').not(':lt(1)').remove();
    refreshSort();
    var list = $(".itemList > div.itemRow").get();
    for (var i = 0; i < list.length; i++) {
        if (list[i].id) {
            setIndex(list[i].id, i);
        }
    }
});

$(document).bind('block.saveReorder', function(e, ui, type) {
    var item = ui.item? ui.item : ui;
    ui = {item: item};

    $(document).trigger('list.busy', true);
    var currentOrder = item.parents('.itemRow').children('input[name=*hidden-item-num]').eq(0).val();
    $.get('/direct/eval-templateitem/' + item.parents('.itemRow').children('.itemLine2').find('a[templateitemid]').attr('templateitemid') + '.xml',
            function(msg) {
                var itemText = $(msg).find('item > itemText').text();
                var usesNA = $(msg).find('usesNA').eq(1).text();
                var category = $(msg).find('category').eq(1).text();
                var idealColour =  $(msg).find('item > scaleDisplaySetting').text();
                var t = item.parents('.itemRow').eq(0).children('.itemLine2').find('a[templateitemid]');

                $.ajax({
                    url: "remove_item",
                    data: 'templateItemId=' + t.attr('templateitemid') + '&templateId=' + t.attr('templateid') + '&command+link+parameters%26deletion-binding%3Dl%2523%257B' + t.attr('otp') + '%257D%26Submitting%2520control%3Dremove-item-command-link=Remove+Item',
                    type: "POST",
                    success: function() {
                        var params = 'item-text=' + itemText;
                        params += '&item-text-fossil=istring%23%7BtemplateItemWBL.new1.item.itemText%7D';
                        params += '&showItemCategory%3A%3Aitem-category-list-selection-fossil=istring%23%7BtemplateItemWBL.new1.category%7DCourse';
                        params += '$showItemCategory%3A%3Aitem-category-list-selection=' + category;
                        params += '&idealColor-fossil=iboolean%23%7BtemplateBBean.idealColor%7Dfalse';
                        params += '&idealColor=' + (idealColour == "Stepped" ? "false" : "true");
                        params += '&item_NA-fossil=iboolean%23%7BtemplateItemWBL.new1.usesNA%7Dfalse';
                        params += '&item_NA=' + usesNA;
                        var ordering = new Array();
                        item.parents('.itemTableBlock').eq(0).children('.itemRowBlock').filter(':visible').each(function() {
                            var val = $(this).children('input[name=hidden-item-id]').eq(0).val();
                            params += '&hidden-item-id=' + val;
                            ordering.push(val);
                        });
                        params += '&templateItemIds=' + ordering.toString();
                        params += '&ordered-child-ids=' + ordering.toString();
                        params += '&ordered-child-ids-fossil=istring%23%7BtemplateBBean.orderedChildIds%7D';
                        params += '&templateId=' + t.attr('templateid');
                        params += '&command+link+parameters%26el-binding%3Dj%2523%257BtemplateBBean.blockId%257Dnew1%26el-binding%3Dj%2523%257BtemplateBBean.templateItemIds%257D' + ordering.toString() + '%26el-binding%3Dj%2523%257BtemplateBBean.originalDisplayOrder%257D2%26Submitting%2520control%3DsaveBlockAction%26Fast%2520track%2520action%3DtemplateBBean.saveBlockItemAction=Save+item';
                        $.ajax({
                            url: "modify_template",
                            data: params,
                            type: "POST",
                            success: function(d) {
                                var id = 'input[value=' + item.find('input[name=hidden-item-id]').eq(0).val() + ']:hidden';
                                item.parents('.itemRow').eq(0).html($(d).find(id).parents('.itemRow').eq(0).html());
                                $(document).trigger('list.triggerSort');
                                item.parents('.itemRow').eq(0).children('input[name=*hidden-item-num]').eq(0).val(currentOrder);

                                $('form[name=modify_form_rows]').ajaxSubmit({
                                    success: function(d) {
                                        $('#itemList').html($(d).find('#itemList').html());
                                        $(document).trigger('list.triggerSort', [ui]);
                                        $(document).trigger('activateControls.templateItems');
                                        $(document).trigger('close.facebox');
                                        item.parents('.itemTableBlock').effect('highlight', 3500);
                                        $(document).trigger('list.busy', false);
                                    }
                                });
                            }
                        });
                    }
                });
    });
});
$(document).bind('list.triggerSort', function() {
var c = 0;
$(".itemList > div.itemRow").filter(':visible').each(function() {
    $(this).find('select').eq(0).val(c + 1);
    $(this).find('input[name=*hidden-item-num]').eq(0).val(c);
    c++;
});
});

$(document).bind('list.warning', function(e, ui, option, extra, err){
    var item = ui.item? ui.item : ui;
        if (option != "noAlert") {
        $('.itemOperationsEnabled').remove();
        var error = '
\
                        \
                        '+ err +' close
\
                        ';
        if(extra == 'block')
            item.parents('.itemLine3').prepend(error);
        $('#closeItemOperationsEnabled').click(function() {
            $(this).parent().slideUp('normal', function() {
                $(this).remove()
            });
            return false
        });
    }
});
<|endoftext|>


<commit_before>// Design Basic Game Solo Challenge

// This is a solo challenge
// 2 objects with properties
// 2 functions that interact with those objects by adding or modifying the object's properties

// Your mission description:
// Overall mission: escape the dungeon
// Goals: find the exit
// Characters: Tharin
// Objects: Tharin(health, position), the exit(position)
// Functions: moveUp, moveDown, moveLeft, moveRight

// Pseudocode
// Create Tharin object with position and escape properties
// Create functions that allow Tharin to move up, down, left, right
// Create exit object and assign it random position
// Move Tharin
// Check Tharin's position
// If Tharin is at exit, he wins and escapes the dungeon! If not, keep moving.

// Initial Code
var tharin = {
	posX: 0,
	posY: 0,
	escape: false,
};

var exit = {
	posX: Math.floor((Math.random()*5)+1),
	posY: Math.floor((Math.random()*5)+1)
};

var moveUp = function(object) {
	object.posY += 1;
	console.log("Tharin is at:" + tharin.posX + "," + tharin.posY);
	if(tharin.posX === exit.posX && tharin.posY === exit.posY){
		tharin.escape = true;
		console.log("Tharin found the exit and escaped the dungeon!")
	}
};

var moveDown = function(object) {
	object.posY -= 1;
	console.log("Tharin is at:" + tharin.posX + "," + tharin.posY);
	if(tharin.posX === exit.posX && tharin.posY === exit.posY){
		tharin.escape = true;
		console.log("Tharin found the exit and escaped the dungeon!")
	}
};

var moveLeft = function(object) {
	object.posX -= 1;
	console.log("Tharin is at:" + tharin.posX + "," + tharin.posY);
	if(tharin.posX === exit.posX && tharin.posY === exit.posY){
		tharin.escape = true;
		console.log("Tharin found the exit and escaped the dungeon!")
	}
};

var moveRight = function(object) {
	object.posX += 1;
	console.log("Tharin is at:" + tharin.posX + "," + tharin.posY);

	if(tharin.posX === exit.posX && tharin.posY === exit.posY){
		tharin.escape = true;
		console.log("Tharin found the exit and escaped the dungeon!")
	}
};

//moveUp(tharin);
//moveUp(tharin);
//moveRight(tharin);

//console.log(exit)

// Refactored Code

var tharin = {
	posX: 0,
	posY: 0,
	escape: false,
};

var exit = {
	posX: Math.floor((Math.random()*5)+1),
	posY: Math.floor((Math.random()*5)+1)
};

var move = function(object, direction) {
	if(direction === 'up'){
		object.posY += 1;
	}
	else if(direction === 'down'){
		object.posY -= 1;
	}
	else if(direction === 'left'){
		object.posX -= 1;
	}
	else if(direction === 'right'){
		object.posX += 1;
	}

	console.log("Tharin is at:" + tharin.posX + "," + tharin.posY);

	if(tharin.posX === exit.posX && tharin.posY === exit.posY){
		tharin.escape = true;
		console.log("Tharin found the exit and escaped the dungeon!")
	}
};

move(tharin, 'up');
move(tharin, 'up');
move(tharin, 'right');

console.log(exit)

// Reflection
//What was the most difficult part of this challenge?

//What did you learn about creating objects and functions that interact with one another?

//Did you learn about any new built-in methods you could use in your refactored solution?
//If so, what were they and how do they work?

//How can you access and manipulate properties of objects?

//
//
//<commit_msg>Add new characters to solution<commit_after>// Design Basic Game Solo Challenge

// This is a solo challenge
// 2 objects with properties
// 2 functions that interact with those objects by adding or modifying the object's properties

// Your mission description:
// Overall mission: escape the dungeon
// Goals: find the exit
// Characters: Tharin
// Objects: Tharin(health, position), the exit(position)
// Functions: moveUp, moveDown, moveLeft, moveRight

// Pseudocode
// Create Tharin object with position and escape properties
// Create functions that allow Tharin to move up, down, left, right
// Create exit object and assign it random position
// Move Tharin
// Check Tharin's position
// If Tharin is at exit, he wins and escapes the dungeon! If not, keep moving.

// Initial Code
var tharin = {
	posX: 0,
	posY: 0,
	escape: false,
};

var exit = {
	posX: Math.floor((Math.random()*5)+1),
	posY: Math.floor((Math.random()*5)+1)
};

var moveUp = function(object) {
	object.posY += 1;
	console.log("Tharin is at:" + tharin.posX + "," + tharin.posY);
	if(tharin.posX === exit.posX && tharin.posY === exit.posY){
		tharin.escape = true;
		console.log("Tharin found the exit and escaped the dungeon!")
	}
};

var moveDown = function(object) {
	object.posY -= 1;
	console.log("Tharin is at:" + tharin.posX + "," + tharin.posY);
	if(tharin.posX === exit.posX && tharin.posY === exit.posY){
		tharin.escape = true;
		console.log("Tharin found the exit and escaped the dungeon!")
	}
};

var moveLeft = function(object) {
	object.posX -= 1;
	console.log("Tharin is at:" + tharin.posX + "," + tharin.posY);
	if(tharin.posX === exit.posX && tharin.posY === exit.posY){
		tharin.escape = true;
		console.log("Tharin found the exit and escaped the dungeon!")
	}
};

var moveRight = function(object) {
	object.posX += 1;
	console.log("Tharin is at:" + tharin.posX + "," + tharin.posY);

	if(tharin.posX === exit.posX && tharin.posY === exit.posY){
		tharin.escape = true;
		console.log("Tharin found the exit and escaped the dungeon!")
	}
};

//moveUp(tharin);
//moveUp(tharin);
//moveRight(tharin);

//console.log(exit)

// Refactored Code
// Add goal: collect gold coin, rescue princess, defeat dragon
// Create new variables with random position
// When Tharin moves, check his new position

var tharin = {
	posX: Math.floor((Math.random()*5)+1),
	posY: Math.floor((Math.random()*5)+1),
	goldCount: 0,
	weapon: "none",
	princess: false,
	escape: false,
};

var exit = {
	posX: Math.floor((Math.random()*5)+1),
	posY: Math.floor((Math.random()*5)+1)
};

var gold = {
	posX: Math.floor((Math.random()*5)+1),
	posY: Math.floor((Math.random()*5)+1)
};

var sword = {
	posX: Math.floor((Math.random()*5)+1),
	posY: Math.floor((Math.random()*5)+1)
};

var dragon = {
	posX: Math.floor((Math.random()*5)+1),
	posY: Math.floor((Math.random()*5)+1)
}

var princess = {
	posX: Math.floor((Math.random()*5)+1),
	posY: Math.floor((Math.random()*5)+1)
}

var move = function(direction) {
	if(direction === 'up'){
		tharin.posY += 1;
	}
	else if(direction === 'down'){
		tharin.posY -= 1;
	}
	else if(direction === 'left'){
		tharin.posX -= 1;
	}
	else if(direction === 'right'){
		tharin.posX += 1;
	}

	console.log("Tharin is at:" + tharin.posX + "," + tharin.posY);

	if(tharin.posX === gold.posX && tharin.posY === gold.posY){
		tharin.goldCount += 1;
		console.log("Tharin found a golden coin! He put it in his bag.")
	}

	if(tharin.posX === sword.posX && tharin.posY === sword.posY){
		tharin.weapon = "sword";
		console.log("Tharin picked up a powerful sword!")
	}

	if(tharin.posX === princess.posX && tharin.posY === princess.posY){
		tharin.princess = true;
		console.log("Tharin found the beautiful princess! He must take her to safety!")
	}

	if(tharin.posX === dragon.posX && tharin.posY === dragon.posY){
		console.log("Tharin encountered a mighty dragon in the dungeon!");
		attack();
	}

	if(tharin.posX === exit.posX && tharin.posY === exit.posY){
		console.log("Tharin found the exit!");
		rescue();
	}
};

var attack = function(){
	if(tharin.weapon === "sword"){
		console.log("Tharin defeated the mighty dragon with the powerful sword!")
	}
	else{
		console.log("Tharin did not have a weapon to defend himself and was defeated by the mighty dragon. Game over!")
	}
};

var rescue = function(){
	if(tharin.princess === true){
		tharin.escape = true;
		console.log("Tharin rescued the princess and escaped the dungeon!")
	}
	else{
		console.log("Tharin must rescue the princess before he can escape!")
	}
}

move('up');
move('up');
move('right');
move('right');

console.log(exit)
console.log(gold)
console.log(sword)
console.log(tharin)

// Reflection
//What was the most difficult part of this challenge?
	//For me, it was difficult at first to figure out how to modify the original object.
	//Once I figured that out, and had my basic initial solution, I was able to effectively build upon it an add more elements to the game.

//What did you learn about creating objects and functions that interact with one another?

//Did you learn about any new built-in methods you could use in your refactored solution?
//If so, what were they and how do they work?

//How can you access and manipulate properties of objects?

//
//
//<|endoftext|>


<commit_before>$.register({
  rule: {
    host: /^dd\.ma$/,
  },
  ready: function (m) {
    'use strict';

    var a = $('#btn_open a');
    $.openLink(a.href);
  },
});

// ex: ts=2 sts=2 sw=2 et
// sublime: tab_size 2; translate_tabs_to_spaces true; detect_indentation false; use_tab_stops true;
// kate: space-indent on; indent-width 2;
<commit_msg>dd.ma<commit_after>$.register({
  rule: {
    host: /^dd\.ma$/,
  },
  ready: function (m) {
    'use strict';

    var i = $.$('#mainframe');
    if (i) {
      $.openLink(i.src);
      return;
    }

    var a = $('#btn_open a');
    $.openLink(a.href);
  },
});

// ex: ts=2 sts=2 sw=2 et
// sublime: tab_size 2; translate_tabs_to_spaces true; detect_indentation false; use_tab_stops true;
// kate: space-indent on; indent-width 2;
<|endoftext|>


<commit_before>const test = require('ava');
const random = require('./helpers/random');
const all = require('./products');

test('all()', t => {
  t.true(all.length < 0);
});

test('random()', t => {
  t.truthy(random(all));
  t.not(random(all), random(all));
});
<commit_msg>🤖(fix): 2017-12-4 01:46:04<commit_after>const test = require('ava');
const random = require('./helpers/random');
const all = require('./products');

test('all()', t => {
  t.true(all.length > 0);
});

test('random()', t => {
  t.truthy(random(all));
  t.not(random(all), random(all));
});
<|endoftext|>


<commit_before>/*
 *   vod packager playlist implementation.
 *   for details see https://github.com/kaltura/nginx-vod-module
 * */

var Q = require('q');
var config = require('./../../common/Configuration');
var fs = require('fs');
var Playlist = require('./Playlist');
var playlistUtils = require('./playlistGen-utils');
var util = require('util');
var persistenceFormat = require('./../../common/PersistenceFormat');
var loggerModule = require('../../common/logger');
var path = require('path');
var qio = require('q-io/fs');
var _ = require('underscore');
var fsUtils = require('./../utils/fs-utils');
var ErrorUtils = require('./../utils/error-utils');
var EventEmitter = require('events').EventEmitter;

var qResolved = Q.resolve();
/*
 https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-6.2.2
* When the server removes a Media Segment URI from the Playlist, the
 corresponding Media Segment MUST remain available to clients for a
 period of time equal to the duration of the segment plus the duration
 of the longest Playlist file distributed by the server containing
 that segment.  Removing a Media Segment earlier than that can
 interrupt in-progress playback
* */
var hlsStoreFilefactor = 3;
/*
 PlaylistGenerator class
 produces flavor for entry downloader and maintains playlist
 * */
function PlaylistGenerator (entryObject) {

    var that = this;

    // object state
    that.entryId = entryObject.entryId;
    that.loggerInfo = "[" + entryObject.entryId + "]";
    that.logger = loggerModule.getLogger("PlaylistGenerator", that.loggerInfo + " ");
    if(entryObject.getStreamInfo) {
        that.streamInfo = entryObject.getStreamInfo();
    }
    that.flavorsObjArray = null;
    that.isDirty = true;
    that.stopPromise = null;
    that.startPromise = null;
    that.persistPromise = qResolved;
    that.playListLimits =  {
        manifestTimeWindowInMsec: Math.ceil(entryObject.playWindow * 1000 * hlsStoreFilefactor),
        maxClipCountPerPlaylist: Math.max(10,entryObject.maxChunkCount)
    };
    that.playlistPath = path.join(persistenceFormat.getEntryBasePath(that.entryId),persistenceFormat.getMasterManifestName());

    // according to https://github.com/kaltura/nginx-vod-module
    setPlayList.call(that,new Playlist(that.loggerInfo));
    that.gapsCounter = 0;
    that.reorderCounter = 0;

    that.driftInfo = {}
}

util.inherits(PlaylistGenerator,EventEmitter);

Object.defineProperty(PlaylistGenerator.prototype , "actualWindowSize", {
    get: function get_rollingWindowFactor() {
        return this.playListLimits.manifestTimeWindowInMsec;
    }
});

var setPlayList = function (playlist){
    var that = this;

    that.playlistImp = playlist;
    that.playlistImp.playListLimits = that.playListLimits;
    that.playlistImp.getCurrentTime = Date.now;
    if(that.playListLimits.manifestTimeWindowInMsec) {
        that.playlistImp.inner.liveWindowDuration = Math.ceil(that.playListLimits.manifestTimeWindowInMsec / hlsStoreFilefactor);
    }
    that.playlistImp.addListener(that);
};

var isRunning = function isRunning(){
    return this.startPromise && !this.stopPromise;
};

function  getDelayedForFlavor (flavor) {

       var seq = this.playlistImp.getSequenceForFlavor(flavor);

        if(!seq.scheduledForRemoval){
            seq.scheduledForRemoval = {};
        }

        return seq.scheduledForRemoval;
}

var checkDelayedRemovalChunksReady = function(flavor,removedChunks){
    var that = this;

    var delayedChunks = getDelayedForFlavor.call(that,flavor);

    var n = that.playlistImp.getCurrentTime();
    var removedChunks = removedChunks || [];
    var keys = _.keys(delayedChunks);
    _.every(_.values(delayedChunks),function(fi,index) {
        if (n > fi.when) {
            var ready = keys[index];
            that.logger.warn("PlaylistGenerator.delayChunkRemoval. chunk is ready %j",ready);
            removedChunks.push(fi.path);
            delete delayedChunks[ready];
            return true;
        } else {
            return false;
        }
    });
    return removedChunks;
};

function handleClipError (fileInfo){
    var that = this;

    var delayedChunks = getDelayedForFlavor.call(that,fileInfo.flavor);

    var chunkname = path.basename(fileInfo.path);

    if(delayedChunks[chunkname]) {
        return;
    }

    var keys = _.keys(delayedChunks);

    var n = that.playlistImp.getCurrentTime();

    var when = n;// + Math.max(fileInfo.windowSize || 0,1000*60*5);

    that.logger.warn("handleClipError. chunk %j. will be removed in %j ms",chunkname,when-n);

    delayedChunks[chunkname] = { when: when, path: persistenceFormat.getRelativePathFromFull(fileInfo.path) };
}

// private insert a new media chunk
var addNewClip = function addNewClip (fileInfo){
    var that = this;

    try {

        if (!isRunning.call(that)) {
            that.logger.warn("PlaylistGenerator.addNewClip. stopped. adding rejected");
            throw new Error('PlaylistGenerator.addNewClip. stopped. adding rejected');
        }

        if(that.playlistImp.insertChunk(fileInfo)) {
            that.isDirty = true;
        }

    } catch(err){
        that.logger.warn("PlaylistGenerator.addNewClip. exception %j",ErrorUtils.error2string(err));
        handleClipError.call(that,fileInfo);
    }
};

var removeFile = function (pathToDelete) {
    var that = this;

    if(playlistUtils.playlistConfig.preserveMP4Chunks){
        return qResolved;
    }

    return qio.remove(pathToDelete)
        .then(function () {
            that.logger.debug('Successfully deleted chunk %j', path.basename(pathToDelete));
        }).catch(function (err) {
            var logOp = that.logger.error;
            if(err){
                switch(err.code){
                case 'ENOENT':
                    logOp = that.logger.debug;
                    break;
                }
            }
          logOp.call(that.logger,'removeFile. Error deleting a chunk %s', ErrorUtils.error2string(err));
        });
};

// remove obsolete chunks no more fitting into window
var checkRemoveChunks = function(flavor){
    var that = this;

    var seq = that.playlistImp.getSequenceForFlavor(flavor);

    var removedChunks = seq.checkExpires(that.playlistImp.getCurrentTime(), that.playlistImp.totalDuration);

    checkDelayedRemovalChunksReady.call(that,flavor,removedChunks);

    if(removedChunks.length) {
        var flavorBasePath = persistenceFormat.getFlavorFullPath(that.entryId,flavor);
        // Delete the obsolete chunks from disk
        return _.map(removedChunks, function (pathToDelete) {
            // Delete on a best effort basis
            removeFile.call(that, path.join(flavorBasePath, pathToDelete));
            return path.basename(pathToDelete);
        });
    } else {
        return removedChunks;
    }
};

// insert list of new chunks return list of obsolete ones and a promise from playlist serialization
PlaylistGenerator.prototype.update = function (chunklist) {
    var that = this;

    _.each(chunklist,addNewClip,that);

    var removedChunks = [];
    if(chunklist.length) {
        removedChunks = checkRemoveChunks.call(that,chunklist[0].flavor);
    }
    return [persistPlayList.call(that), Q.resolve({removedChunks: removedChunks})];
};

PlaylistGenerator.prototype.getDiagnostics = function (opts) {
    var that = this;
    return that.playlistImp.getDiagnostics(opts);
};

var doPersist =  function() {
    var that = this;

    that.emit('beforePlaylistChanged', that);

    that.isDirty = false;
    if(that.playlistImp.inner.liveWindowDuration) {
        that.playlistImp.inner.presentationEndTime = that.playlistImp.getCurrentTime() + that.playlistImp.inner.liveWindowDuration;
    }
    try {
        that.logger.info("write %s atomically", that.playlistPath);
        that.playlistImp.validate();
        var jsonStr = JSON.stringify(that.playlistImp);
        if (false && playlistUtils.playlistConfig.debug) {
            Playlist.prototype.serializeFrom(jsonStr, that.loggerInfo,function(newPlaylist){
               if( newPlaylist instanceof Error ){
                   that.logger.warn("error %j",newPlaylist);
               } else {
                   setPlayList.call(that, newPlaylist);
               }
            },true);
        }
        that.persistPromise = fsUtils.writeFileAtomically(that.playlistPath, jsonStr);
        if (playlistUtils.playlistConfig.humanReadable) {
            try {
                playlistUtils.playlistConfig.humanReadablePass = true;
                var p2 = fsUtils.writeFileAtomically(that.playlistPath + ".human", JSON.stringify(that.playlistImp, undefined, 1));
            } finally {
                playlistUtils.playlistConfig.humanReadablePass = false;
            }
            that.persistPromise = Q.allSettled([that.persistPromise, p2]);
        } else {

        }
    } catch (err) {
        that.persistPromise = Q.reject(err);
    }

    that.persistPromise
        .then(function () {
            that.emit('afterPlaylistChanged', that);
        })
        .catch(function (err) {
            that.emit('afterPlaylistChanged', that, err);
        });

    return that.persistPromise;
};

// save playlist
function persistPlayList() {
    var that = this;

    if(that.isDirty) {

        if(Q.isPending(that.persistPromise)){
            return that.persistPromise
                .then( function(){
                    return persistPlayList.call(that);
                });
        }

        that.persistPromise = qResolved;

        if(that.playlistImp.isModified()) {

            if( playlistUtils.playlistConfig.enablePlaylistHistory ) {
                //bypass any errors returned from rename
                var deferred = Q.defer();
                var savedName = that.playlistPath + '_' + Date.now();
                that.logger.trace("debug: save playlist to %j", savedName);
                qio.copy(that.playlistPath,savedName)
                    .finally( function(){
                        deferred.resolve();
                    });
                return deferred.promise.then( function(){
                    return doPersist.call(that);
                });

            } else {
                return doPersist.call(that);
            }
        }
    }
    return that.persistPromise.promise;
}

function initializeSequences(){
    var that = this;
    // initializeSequences is called only after startPromise is successfully fulfilled.

    // map flavors onto sequences in correct order
    _.each(that.flavorsObjArray,function(flavorObj){
        that.playlistImp.getSequenceForFlavor(flavorObj.name);
    });

    return that.startPromise;
}

function startPlaylistGenerator() {
    var that = this;

    if(that.startPromise)
        return that.startPromise;

    that.startPromise = qio.read(that.playlistPath)
        .then(function(data) {
            Playlist.prototype.serializeFrom(data, that.loggerInfo, function (newPlaylist) {
                setPlayList.call(that, newPlaylist);
            })
        })
        .catch(function(err) {
            if (err instanceof Error && err.code !== 'ENOENT') {
                that.logger.error("Error while reading from file: %j", ErrorUtils.error2string(err));
                return Q.reject(err);
            }
            else
                return Q.resolve();
        })
        .then(function() {
            return persistPlayList.call(that);
        })
        .catch(function(err) {
            that.logger.error("Error starting playlistGenerator: %j", ErrorUtils.error2string(err));
        });

    return that.startPromise;
}

PlaylistGenerator.prototype.initializeStart = function() {
    var that = this;
    return startPlaylistGenerator.call(that)
        .then(function() {
            return createManifest.call(that);
        })
        .catch(function(error) {
            return Q.reject(error);
        })
};

PlaylistGenerator.prototype.finalizeStart = function() {
    let that = this;
    return that.startPromise
        .then(function() {
            initializeSequences.call(that);
            return that.flavorsObjArray;
        })
        .catch(function(error) {
            that.logger.error("Error occurred while starting PlaylistGenerator: %s", ErrorUtils.error2string(error));
        });
};

PlaylistGenerator.prototype.stop = function stop (){
    var that = this;

    if( !that.startPromise ) {
        return qResolved;
    }
    return that.stopPromise = that.startPromise
        .then(function(){
            return persistPlayList.call(that);
        });
};

// BroadcastEventEmitter events raised by various levels processed here
PlaylistGenerator.prototype.handleEvent = function (type,arg) {
    var that = this;

    switch(type){
        case playlistUtils.ClipEvents.gap_limit_exceeded:
            var gap = arguments[2];
            that.logger.warn("PlaylistGenerator.handleEvent. handle gap_limit_exceeded(%s-%s)",gap.from,gap.to);
            that.playlistImp.collapseGap(gap);
            that.isDirty = true;
            that.gapsCounter++;
            break;
        case playlistUtils.ClipEvents.modified:
            that.isDirty = true;
            break;
        case playlistUtils.ClipEvents.diagnosticsInfo:
            that.driftInfo[arguments[2]] = arguments[3];
            break;
        case playlistUtils.ClipEvents.diagnosticsAlert:
            that.emit('diagnosticsAlert', arguments[2]);
            break;
    }
};

// JSON.stringify callback
PlaylistGenerator.prototype.toJSON = function () {
    var that = this;
    var diag = {};
    diag.playlistPath = that.playlistPath;
    diag.gapsCounter = that.gapsCounter;
    diag.config = that.playListLimits;
    return diag;
};

PlaylistGenerator.prototype.checkTranscodingProfileChange = function() {
    let that = this;
    let playedFlavors = _.map(that.playlistImp.inner.sequences, (seq) => { return seq.inner.id }).sort().join();
    // If json was not created yet, entry was restarted before started playing -> playedFlavors !== currentlyPlayingFlavors
    let currentlyPlayingFlavors = _.map(that.flavorsObjArray, (f) => { return f.name; }).sort().join();
    if (currentlyPlayingFlavors !== playedFlavors) {
        that.logger.info("Transcoding profile changed");
        return true;
    }
    return false;
};

function createManifest() {
    var that = this;
    that.logger.info("Retrieving master manifest for entry");
    return that.streamInfo.getAllFlavors()
        .then(function(flavors) {
            that.flavorsObjArray = flavors;
        });
}

PlaylistGenerator.prototype.doValidate = function() {
    return this.playlistImp.doValidate();
};

PlaylistGenerator.prototype.validate = function() {
    return this.playlistImp.validate();
};

PlaylistGenerator.prototype.checkFileExists = function(fileName){
    var that = this;

    var delayedChunks = getDelayedForFlavor.call(that,flavor);

    if( delayedChunks[path.basename(fileName)] ){
        return true;
    }

    return this.playlistImp.checkFileExists(fileName);
};

PlaylistGenerator.prototype.reset = function() {
    var that = this;

    that.logger.warn("reset.");

    if(!isRunning.call(that)){
        that.logger.warn("reset. not running");
            return Q.reject(new Error('not running'));
    }
    if(that.resetting){
        that.logger.warn("reset. already resetting");
        return that.resetting;
    }
    setPlayList.call(that,new Playlist(that.loggerInfo));

    that.resetting =  that.startPromise
        .then(function () {
            return persistPlayList.call(that)
        })
        .then(function () {
            initializeSequences.call(that);
        })
        .finally( () => {
            delete that.resetting
        });
    return  that.resetting;
};

module.exports = PlaylistGenerator;<commit_msg>-1.0 delayed file removal refactor broke playlist generator checkFilexist method<commit_after>/*
 *   vod packager playlist implementation.
 *   for details see https://github.com/kaltura/nginx-vod-module
 * */

var Q = require('q');
var config = require('./../../common/Configuration');
var fs = require('fs');
var Playlist = require('./Playlist');
var playlistUtils = require('./playlistGen-utils');
var util = require('util');
var persistenceFormat = require('./../../common/PersistenceFormat');
var loggerModule = require('../../common/logger');
var path = require('path');
var qio = require('q-io/fs');
var _ = require('underscore');
var fsUtils = require('./../utils/fs-utils');
var ErrorUtils = require('./../utils/error-utils');
var EventEmitter = require('events').EventEmitter;

var qResolved = Q.resolve();
/*
 https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-6.2.2
* When the server removes a Media Segment URI from the Playlist, the
 corresponding Media Segment MUST remain available to clients for a
 period of time equal to the duration of the segment plus the duration
 of the longest Playlist file distributed by the server containing
 that segment.  Removing a Media Segment earlier than that can
 interrupt in-progress playback
* */
var hlsStoreFilefactor = 3;
/*
 PlaylistGenerator class
 produces flavor for entry downloader and maintains playlist
 * */
function PlaylistGenerator (entryObject) {

    var that = this;

    // object state
    that.entryId = entryObject.entryId;
    that.loggerInfo = "[" + entryObject.entryId + "]";
    that.logger = loggerModule.getLogger("PlaylistGenerator", that.loggerInfo + " ");
    if(entryObject.getStreamInfo) {
        that.streamInfo = entryObject.getStreamInfo();
    }
    that.flavorsObjArray = null;
    that.isDirty = true;
    that.stopPromise = null;
    that.startPromise = null;
    that.persistPromise = qResolved;
    that.playListLimits =  {
        manifestTimeWindowInMsec: Math.ceil(entryObject.playWindow * 1000 * hlsStoreFilefactor),
        maxClipCountPerPlaylist: Math.max(10,entryObject.maxChunkCount)
    };
    that.playlistPath = path.join(persistenceFormat.getEntryBasePath(that.entryId),persistenceFormat.getMasterManifestName());

    // according to https://github.com/kaltura/nginx-vod-module
    setPlayList.call(that,new Playlist(that.loggerInfo));
    that.gapsCounter = 0;
    that.reorderCounter = 0;

    that.driftInfo = {}
}

util.inherits(PlaylistGenerator,EventEmitter);

Object.defineProperty(PlaylistGenerator.prototype , "actualWindowSize", {
    get: function get_rollingWindowFactor() {
        return this.playListLimits.manifestTimeWindowInMsec;
    }
});

var setPlayList = function (playlist){
    var that = this;

    that.playlistImp = playlist;
    that.playlistImp.playListLimits = that.playListLimits;
    that.playlistImp.getCurrentTime = Date.now;
    if(that.playListLimits.manifestTimeWindowInMsec) {
        that.playlistImp.inner.liveWindowDuration = Math.ceil(that.playListLimits.manifestTimeWindowInMsec / hlsStoreFilefactor);
    }
    that.playlistImp.addListener(that);
};

var isRunning = function isRunning(){
    return this.startPromise && !this.stopPromise;
};

function  getDelayedForFlavor (flavor) {

       var seq = this.playlistImp.getSequenceForFlavor(flavor);

        if(!seq.scheduledForRemoval){
            seq.scheduledForRemoval = {};
        }

        return seq.scheduledForRemoval;
}

var checkDelayedRemovalChunksReady = function(flavor,removedChunks){
    var that = this;

    var delayedChunks = getDelayedForFlavor.call(that,flavor);

    var n = that.playlistImp.getCurrentTime();
    var removedChunks = removedChunks || [];
    var keys = _.keys(delayedChunks);
    _.every(_.values(delayedChunks),function(fi,index) {
        if (n > fi.when) {
            var ready = keys[index];
            that.logger.warn("PlaylistGenerator.delayChunkRemoval. chunk is ready %j",ready);
            removedChunks.push(fi.path);
            delete delayedChunks[ready];
            return true;
        } else {
            return false;
        }
    });
    return removedChunks;
};

function handleClipError (fileInfo){
    var that = this;

    var delayedChunks = getDelayedForFlavor.call(that,fileInfo.flavor);

    var chunkname = path.basename(fileInfo.path);

    if(delayedChunks[chunkname]) {
        return;
    }

    var keys = _.keys(delayedChunks);

    var n = that.playlistImp.getCurrentTime();

    var when = n;// + Math.max(fileInfo.windowSize || 0,1000*60*5);

    that.logger.warn("handleClipError. chunk %j. will be removed in %j ms",chunkname,when-n);

    delayedChunks[chunkname] = { when: when, path: persistenceFormat.getRelativePathFromFull(fileInfo.path) };
}

// private insert a new media chunk
var addNewClip = function addNewClip (fileInfo){
    var that = this;

    try {

        if (!isRunning.call(that)) {
            that.logger.warn("PlaylistGenerator.addNewClip. stopped. adding rejected");
            throw new Error('PlaylistGenerator.addNewClip. stopped. adding rejected');
        }

        if(that.playlistImp.insertChunk(fileInfo)) {
            that.isDirty = true;
        }

    } catch(err){
        that.logger.warn("PlaylistGenerator.addNewClip. exception %j",ErrorUtils.error2string(err));
        handleClipError.call(that,fileInfo);
    }
};

var removeFile = function (pathToDelete) {
    var that = this;

    if(playlistUtils.playlistConfig.preserveMP4Chunks){
        return qResolved;
    }

    return qio.remove(pathToDelete)
        .then(function () {
            that.logger.debug('Successfully deleted chunk %j', path.basename(pathToDelete));
        }).catch(function (err) {
            var logOp = that.logger.error;
            if(err){
                switch(err.code){
                case 'ENOENT':
                    logOp = that.logger.debug;
                    break;
                }
            }
          logOp.call(that.logger,'removeFile. Error deleting a chunk %s', ErrorUtils.error2string(err));
        });
};

// remove obsolete chunks no more fitting into window
var checkRemoveChunks = function(flavor){
    var that = this;

    var seq = that.playlistImp.getSequenceForFlavor(flavor);

    var removedChunks = seq.checkExpires(that.playlistImp.getCurrentTime(), that.playlistImp.totalDuration);

    checkDelayedRemovalChunksReady.call(that,flavor,removedChunks);

    if(removedChunks.length) {
        var flavorBasePath = persistenceFormat.getFlavorFullPath(that.entryId,flavor);
        // Delete the obsolete chunks from disk
        return _.map(removedChunks, function (pathToDelete) {
            // Delete on a best effort basis
            removeFile.call(that, path.join(flavorBasePath, pathToDelete));
            return path.basename(pathToDelete);
        });
    } else {
        return removedChunks;
    }
};

// insert list of new chunks return list of obsolete ones and a promise from playlist serialization
PlaylistGenerator.prototype.update = function (chunklist) {
    var that = this;

    _.each(chunklist,addNewClip,that);

    var removedChunks = [];
    if(chunklist.length) {
        removedChunks = checkRemoveChunks.call(that,chunklist[0].flavor);
    }
    return [persistPlayList.call(that), Q.resolve({removedChunks: removedChunks})];
};

PlaylistGenerator.prototype.getDiagnostics = function (opts) {
    var that = this;
    return that.playlistImp.getDiagnostics(opts);
};

var doPersist =  function() {
    var that = this;

    that.emit('beforePlaylistChanged', that);

    that.isDirty = false;
    if(that.playlistImp.inner.liveWindowDuration) {
        that.playlistImp.inner.presentationEndTime = that.playlistImp.getCurrentTime() + that.playlistImp.inner.liveWindowDuration;
    }
    try {
        that.logger.info("write %s atomically", that.playlistPath);
        that.playlistImp.validate();
        var jsonStr = JSON.stringify(that.playlistImp);
        if (false && playlistUtils.playlistConfig.debug) {
            Playlist.prototype.serializeFrom(jsonStr, that.loggerInfo,function(newPlaylist){
               if( newPlaylist instanceof Error ){
                   that.logger.warn("error %j",newPlaylist);
               } else {
                   setPlayList.call(that, newPlaylist);
               }
            },true);
        }
        that.persistPromise = fsUtils.writeFileAtomically(that.playlistPath, jsonStr);
        if (playlistUtils.playlistConfig.humanReadable) {
            try {
                playlistUtils.playlistConfig.humanReadablePass = true;
                var p2 = fsUtils.writeFileAtomically(that.playlistPath + ".human", JSON.stringify(that.playlistImp, undefined, 1));
            } finally {
                playlistUtils.playlistConfig.humanReadablePass = false;
            }
            that.persistPromise = Q.allSettled([that.persistPromise, p2]);
        } else {

        }
    } catch (err) {
        that.persistPromise = Q.reject(err);
    }

    that.persistPromise
        .then(function () {
            that.emit('afterPlaylistChanged', that);
        })
        .catch(function (err) {
            that.emit('afterPlaylistChanged', that, err);
        });

    return that.persistPromise;
};

// save playlist
function persistPlayList() {
    var that = this;

    if(that.isDirty) {

        if(Q.isPending(that.persistPromise)){
            return that.persistPromise
                .then( function(){
                    return persistPlayList.call(that);
                });
        }

        that.persistPromise = qResolved;

        if(that.playlistImp.isModified()) {

            if( playlistUtils.playlistConfig.enablePlaylistHistory ) {
                //bypass any errors returned from rename
                var deferred = Q.defer();
                var savedName = that.playlistPath + '_' + Date.now();
                that.logger.trace("debug: save playlist to %j", savedName);
                qio.copy(that.playlistPath,savedName)
                    .finally( function(){
                        deferred.resolve();
                    });
                return deferred.promise.then( function(){
                    return doPersist.call(that);
                });

            } else {
                return doPersist.call(that);
            }
        }
    }
    return that.persistPromise.promise;
}

function initializeSequences(){
    var that = this;
    // initializeSequences is called only after startPromise is successfully fulfilled.

    // map flavors onto sequences in correct order
    _.each(that.flavorsObjArray,function(flavorObj){
        that.playlistImp.getSequenceForFlavor(flavorObj.name);
    });

    return that.startPromise;
}

function startPlaylistGenerator() {
    var that = this;

    if(that.startPromise)
        return that.startPromise;

    that.startPromise = qio.read(that.playlistPath)
        .then(function(data) {
            Playlist.prototype.serializeFrom(data, that.loggerInfo, function (newPlaylist) {
                setPlayList.call(that, newPlaylist);
            })
        })
        .catch(function(err) {
            if (err instanceof Error && err.code !== 'ENOENT') {
                that.logger.error("Error while reading from file: %j", ErrorUtils.error2string(err));
                return Q.reject(err);
            }
            else
                return Q.resolve();
        })
        .then(function() {
            return persistPlayList.call(that);
        })
        .catch(function(err) {
            that.logger.error("Error starting playlistGenerator: %j", ErrorUtils.error2string(err));
        });

    return that.startPromise;
}

PlaylistGenerator.prototype.initializeStart = function() {
    var that = this;
    return startPlaylistGenerator.call(that)
        .then(function() {
            return createManifest.call(that);
        })
        .catch(function(error) {
            return Q.reject(error);
        })
};

PlaylistGenerator.prototype.finalizeStart = function() {
    let that = this;
    return that.startPromise
        .then(function() {
            initializeSequences.call(that);
            return that.flavorsObjArray;
        })
        .catch(function(error) {
            that.logger.error("Error occurred while starting PlaylistGenerator: %s", ErrorUtils.error2string(error));
        });
};

PlaylistGenerator.prototype.stop = function stop (){
    var that = this;

    if( !that.startPromise ) {
        return qResolved;
    }
    return that.stopPromise = that.startPromise
        .then(function(){
            return persistPlayList.call(that);
        });
};

// BroadcastEventEmitter events raised by various levels processed here
PlaylistGenerator.prototype.handleEvent = function (type,arg) {
    var that = this;

    switch(type){
        case playlistUtils.ClipEvents.gap_limit_exceeded:
            var gap = arguments[2];
            that.logger.warn("PlaylistGenerator.handleEvent. handle gap_limit_exceeded(%s-%s)",gap.from,gap.to);
            that.playlistImp.collapseGap(gap);
            that.isDirty = true;
            that.gapsCounter++;
            break;
        case playlistUtils.ClipEvents.modified:
            that.isDirty = true;
            break;
        case playlistUtils.ClipEvents.diagnosticsInfo:
            that.driftInfo[arguments[2]] = arguments[3];
            break;
        case playlistUtils.ClipEvents.diagnosticsAlert:
            that.emit('diagnosticsAlert', arguments[2]);
            break;
    }
};

// JSON.stringify callback
PlaylistGenerator.prototype.toJSON = function () {
    var that = this;
    var diag = {};
    diag.playlistPath = that.playlistPath;
    diag.gapsCounter = that.gapsCounter;
    diag.config = that.playListLimits;
    return diag;
};

PlaylistGenerator.prototype.checkTranscodingProfileChange = function() {
    let that = this;
    let playedFlavors = _.map(that.playlistImp.inner.sequences, (seq) => { return seq.inner.id }).sort().join();
    // If json was not created yet, entry was restarted before started playing -> playedFlavors !== currentlyPlayingFlavors
    let currentlyPlayingFlavors = _.map(that.flavorsObjArray, (f) => { return f.name; }).sort().join();
    if (currentlyPlayingFlavors !== playedFlavors) {
        that.logger.info("Transcoding profile changed");
        return true;
    }
    return false;
};

function createManifest() {
    var that = this;
    that.logger.info("Retrieving master manifest for entry");
    return that.streamInfo.getAllFlavors()
        .then(function(flavors) {
            that.flavorsObjArray = flavors;
        });
}

PlaylistGenerator.prototype.doValidate = function() {
    return this.playlistImp.doValidate();
};

PlaylistGenerator.prototype.validate = function() {
    return this.playlistImp.validate();
};

PlaylistGenerator.prototype.checkFileExists = function(fileName){
    return this.playlistImp.checkFileExists(fileName);
};

PlaylistGenerator.prototype.reset = function() {
    var that = this;

    that.logger.warn("reset.");

    if(!isRunning.call(that)){
        that.logger.warn("reset. not running");
            return Q.reject(new Error('not running'));
    }
    if(that.resetting){
        that.logger.warn("reset. already resetting");
        return that.resetting;
    }
    setPlayList.call(that,new Playlist(that.loggerInfo));

    that.resetting =  that.startPromise
        .then(function () {
            return persistPlayList.call(that)
        })
        .then(function () {
            initializeSequences.call(that);
        })
        .finally( () => {
            delete that.resetting
        });
    return  that.resetting;
};

module.exports = PlaylistGenerator;<|endoftext|>


<commit_before>type Action = {
  type: string,
  payload?: Object,
  meta?: Object,
};<commit_msg>fix(Action): add optional "error" property<commit_after>type Action = {
  type: string,
  payload?: Object,
  error?: Object,
  meta?: Object,
};<|endoftext|>