<commit_before>// multi-resource collections.js...
// there are more comments in single resource collections.js!

$( document ).ready(function() {

    //open collection modal
    $("#collection-modal-btn").click(function () {
        $(".collectionModalBackground").show();
    });

    //close the modal. unselect and get the collections list again
    $(".modalClose").click(function () {
        $(".collectionSearchBar").addClass("first");
        $(".collectionModalBackground").hide();
        $("#collectionModal").show();
        $("#addedCollectionModal").hide();
        var retunselect = unselect(null);
    });

    //go to the arcs collection page from collection added modal.
    $(".viewCollection").click(function () {
		var href = $('#resources').attr('href');
		href = href.split('/');
		href = href.pop();
		window.location.href=arcs.baseURL+"collections/"+href+"?"+$('.viewCollection').attr('data-colId');
    });
    //close the collection added modal
    $(".backToSearch").click(function () {
        $(".modalClose").trigger("click");
    });

    //new collection submit
    $(".collectionNewSubmit").click(function () {
        // creates a new collection and add the other resources to the new one.
        var resource_kids = [];
        $('.resource-container-level').find('.other-resource').each(function (){
            var resource_kid = $(this).attr('id');
            resource_kid = resource_kid.replace('identifier-', '');
            resource_kids.push( resource_kid );
        });
        var formdata = {
            title: $('#collectionTitle').val(),
            resource_kid: resource_kids.shift(),
            description: "",
            public: 1
        };
        $.ajax({
            url: arcs.baseURL + "collections/add",
            type: "POST",
            data: formdata,
            statusCode: {
                201: function (data) {
                    var collection_id = data['collection_id'];
					$('.viewCollection').attr('data-colId', data.collection_id);
                    var intSuccess = 0;
                    var length = resource_kids.length;
                    for(var i=0; i <resource_kids.length; i++){
                        var resource_kid = resource_kids[i];
                        var formdata = {
                            collection: collection_id,  //a collection_id
                            resource_kid: resource_kid
                        }
                        $.ajax({
                            url: arcs.baseURL + "collections/addToExisting",
                            type: "POST",
                            data: formdata,
                            statusCode: {
                                201: function (data) {
                                    intSuccess++;
                                    if( intSuccess == length ) {
                                        //var text = $("label[for=" + lastCheckedId + "]").children(":first").text();
                                        $("#collectionName").text($('#collectionTitle').val());
                                        $("#collectionModal").hide();
                                        $("#addedCollectionModal").show();
                                        getCollections();
                                    }
                                }
                            }
                        });
                    }
                },
                400: function () {
                    console.log("Bad Request");
                    $(".collectionModalBackground").hide();
                },
                405: function () {
                    console.log("Method Not Allowed");
                    $(".collectionModalBackground").hide();
                }
            }
        });
    });

    //add the resource to an existing collection from the search tab.
    $(".collectionSearchSubmit").click(function () {
        // creates 1+ collection entries based on the resource (IE adds the resource to old collections)

        var resource_kids = [];
        $('.resource-container-level').find('.other-resource').each(function (){
            var resource_kid = $(this).attr('id');
            resource_kid = resource_kid.replace('identifier-', '');
            resource_kids.push( resource_kid );
        });
        var intSuccess = 0;
        var length = resource_kids.length;
        for(var i=0; i <resource_kids.length; i++){
            var resource_kid = resource_kids[i];
            var formdata = {
                collection: $('#collectionSearchObjects input:checked').val(),  //a collection_id
                resource_kid: resource_kid
            }
            $.ajax({
                url: arcs.baseURL + "collections/addToExisting",
                type: "POST",
                data: formdata,
                statusCode: {
                    201: function (data) {
						$('.viewCollection').attr('data-colId', data.collection_id);
                        intSuccess++;
                        if( intSuccess == length ) {
                            var text = $("label[for=" + lastCheckedId + "]").children(":first").text();
                            //$("#collectionName").text($('#collectionTitle').val());
                            $("#collectionName").text(text);
                            $("#collectionModal").hide();
                            $("#addedCollectionModal").show();
                            getCollections();
                        }
                    }
                }
            });
        }
    });

    // collection tabs
    $(".collectionTabSearch").click(function () {
        $(".collectionSearchContainer").show();
        $(".collectionNewContainer").hide();
        $(".collectionTabSearch").addClass("activeTab");
        $(".collectionTabNew").removeClass("activeTab");
    });

    $(".collectionTabNew").click(function () {
        $(".collectionNewContainer").show();
        $(".collectionSearchContainer").hide();
        $(".collectionTabNew").addClass("activeTab");
        $(".collectionTabSearch").removeClass("activeTab");
    });

    // run on page load
    $(".collectionNewContainer").hide();
    collectionList();
    //getCollections();

    $('.resource-container-level').click(function() {
       getCollections();
    });

});

//collections globals.
var collectionArray = [];
var isAnyChecked = 0;
var lastCheckedId = '';

//get collection list for search modal
function collectionList() {
    var pKid = $('#project1').attr('data-kid');
    collectionArray = [];
    $.ajax({
        url: arcs.baseURL + "collections/titlesAndIds",
        type: "get",
        data: {pKid: pKid},
        success: function (data) {
            data.forEach(function (tempdata) {
                var temparray = $.map(tempdata, function (value, index) {
                    return [value];
                });
                collectionArray.push(temparray);
            })
            collectionsSearch();
        }
    });
}

//unselect collections.
var unselect = function (trigger) {
    if (trigger == null) {
        trigger = true
    }
    this.$(".result").removeClass("selected");
    this.$(".select-button").removeClass("de-select");
    this.$(".select-button, #toggle-select").html("SELECT");
    this.$("#deselect-all").attr({id: "select-all"});
    this.$(".checkedboxes").prop("checked", false);
    this.$("#collectionTitle").val('');
    this.$(".collectionTabSearch").trigger("click");
    collectionList();
    checkSearchSubmitBtn();
}

// Hide/show add to collection button in collection modal
function checkSearchSubmitBtn() {
    // Hide add to collection button in collection modal when no collections are selected
    var checkboxes = $("#collectionSearchObjects > input");
    var submitButt = $(".collectionSearchSubmit");

    if (checkboxes.is(":checked")) {
        submitButt.show();
        isAnyChecked = 1;
    }
    else {
        submitButt.hide();
        isAnyChecked = 0;
    }
}

//update the collection search modal based on the new collection list.
function collectionsSearch() {
    var query = "";
    if ($(".collectionSearchBar").hasClass("first")) {
        query = "";
        $(".collectionSearchBar").removeClass("first");
    } else {
        query = $(".collectionSearchBar").val();
    }

    // only put collections in between the div if they include the query.
    // I.E. "" is in every collection title and user_name
    var populateCheckboxes = "
";
    for (var i = 0; i < collectionArray.length; i++) {
        if ((collectionArray[i][0].toLowerCase()).indexOf(query.toLowerCase()) != -1 ||
            (collectionArray[i][2].toLowerCase()).indexOf(query.toLowerCase()) != -1) {

            populateCheckboxes += "<input type='checkbox' class='checkedboxes' name='item-" + i + "' id='item-" + i + "' value='" + collectionArray[i][1] + "' />"
                + "
" + collectionArray[i][0] + "

" + collectionArray[i][2] + "

";
        }
    }
    $("#collectionSearchObjects").html(populateCheckboxes);

    var checkboxes = $("#collectionSearchObjects > input");
    checkboxes.click(function () {
        if (isAnyChecked == 1) {
            $('#' + lastCheckedId).prop("checked", false);
        }
        lastCheckedId = $(this).attr('id');
        checkSearchSubmitBtn();
    });
    $('#collectionTitle').bind('input propertychange', function () {
        if (this.value != "") {
            $(".collectionNewSubmit").show();
        } else {
            $(".collectionNewSubmit").hide();
        }
    });
}

//for the details tab, what collections the resource is a part of.
function getCollections() {
    var resource_kid = $('.resource-container-level').find('img').filter(function () { //by border
        return $(this).css('border-width') == '5px';
    });
    resource_kid = resource_kid.attr('id');
    resource_kid = resource_kid.replace('identifier-', '');
    var ctab = $("#collections_tab");
    ctab[0].innerHTML = "COLLECTIONS (" + 0 + ")";
    $("#collections_table").html('');
    $.ajax({
        url: arcs.baseURL + "collections/memberships",
        type: "get",
        data: {
            id: resource_kid
        },
        success: function (data) {
            var numCollections = 0;
            numCollections = data.collections.length;
            if (data.collections.length > 0) {
                var populateCollections = "

" +
                    "
This resource is a part of the following " + numCollections + " collections...
";
                for (var i = numCollections - 1; i >= 0; i--) {
                    var collection = data.collections[i];
                    populateCollections += "
" + collection.title + "	" + collection.user_name + "
";
                }
                populateCollections += "

";
            } else {
                var populateCollections = "

" +
                    "
This resource isn't part of any collections...
";
            }
            ctab[0].innerHTML = "COLLECTIONS (" + numCollections + ")";
            $("#collections_table").html(populateCollections);
        }
    })
}<commit_msg>Fix for the old multi-resource accordion bug.<commit_after>// multi-resource collections.js...
// there are more comments in single resource collections.js!

$( document ).ready(function() {

    //open collection modal
    $("#collection-modal-btn").click(function () {
        $(".collectionModalBackground").show();
    });

    //close the modal. unselect and get the collections list again
    $(".modalClose").click(function () {
        $(".collectionSearchBar").addClass("first");
        $(".collectionModalBackground").hide();
        $("#collectionModal").show();
        $("#addedCollectionModal").hide();
        var retunselect = unselect(null);
    });

    //go to the arcs collection page from collection added modal.
    $(".viewCollection").click(function () {
		var href = $('#resources').attr('href');
		href = href.split('/');
		href = href.pop();
		window.location.href=arcs.baseURL+"collections/"+href+"?"+$('.viewCollection').attr('data-colId');
    });
    //close the collection added modal
    $(".backToSearch").click(function () {
        $(".modalClose").trigger("click");
    });

    //new collection submit
    $(".collectionNewSubmit").click(function () {
        // creates a new collection and add the other resources to the new one.
        var resource_kids = [];
        $('.resource-container-level').find('.other-resource').each(function (){
            var resource_kid = $(this).attr('id');
            resource_kid = resource_kid.replace('identifier-', '');
            resource_kids.push( resource_kid );
        });
        var formdata = {
            title: $('#collectionTitle').val(),
            resource_kid: resource_kids.shift(),
            description: "",
            public: 1
        };
        $.ajax({
            url: arcs.baseURL + "collections/add",
            type: "POST",
            data: formdata,
            statusCode: {
                201: function (data) {
                    var collection_id = data['collection_id'];
					$('.viewCollection').attr('data-colId', data.collection_id);
                    var intSuccess = 0;
                    var length = resource_kids.length;
                    for(var i=0; i <resource_kids.length; i++){
                        var resource_kid = resource_kids[i];
                        var formdata = {
                            collection: collection_id,  //a collection_id
                            resource_kid: resource_kid
                        }
                        $.ajax({
                            url: arcs.baseURL + "collections/addToExisting",
                            type: "POST",
                            data: formdata,
                            statusCode: {
                                201: function (data) {
                                    intSuccess++;
                                    if( intSuccess == length ) {
                                        //var text = $("label[for=" + lastCheckedId + "]").children(":first").text();
                                        $("#collectionName").text($('#collectionTitle').val());
                                        $("#collectionModal").hide();
                                        $("#addedCollectionModal").show();
                                        getCollections();
                                    }
                                }
                            }
                        });
                    }
                },
                400: function () {
                    console.log("Bad Request");
                    $(".collectionModalBackground").hide();
                },
                405: function () {
                    console.log("Method Not Allowed");
                    $(".collectionModalBackground").hide();
                }
            }
        });
    });

    //add the resource to an existing collection from the search tab.
    $(".collectionSearchSubmit").click(function () {
        // creates 1+ collection entries based on the resource (IE adds the resource to old collections)

        var resource_kids = [];
        $('.resource-container-level').find('.other-resource').each(function (){
            var resource_kid = $(this).attr('id');
            resource_kid = resource_kid.replace('identifier-', '');
            resource_kids.push( resource_kid );
        });
        var intSuccess = 0;
        var length = resource_kids.length;
        for(var i=0; i <resource_kids.length; i++){
            var resource_kid = resource_kids[i];
            var formdata = {
                collection: $('#collectionSearchObjects input:checked').val(),  //a collection_id
                resource_kid: resource_kid
            }
            $.ajax({
                url: arcs.baseURL + "collections/addToExisting",
                type: "POST",
                data: formdata,
                statusCode: {
                    201: function (data) {
						$('.viewCollection').attr('data-colId', data.collection_id);
                        intSuccess++;
                        if( intSuccess == length ) {
                            var text = $("label[for=" + lastCheckedId + "]").children(":first").text();
                            //$("#collectionName").text($('#collectionTitle').val());
                            $("#collectionName").text(text);
                            $("#collectionModal").hide();
                            $("#addedCollectionModal").show();
                            getCollections();
                        }
                    }
                }
            });
        }
    });

    // collection tabs
    $(".collectionTabSearch").click(function () {
        $(".collectionSearchContainer").show();
        $(".collectionNewContainer").hide();
        $(".collectionTabSearch").addClass("activeTab");
        $(".collectionTabNew").removeClass("activeTab");
    });

    $(".collectionTabNew").click(function () {
        $(".collectionNewContainer").show();
        $(".collectionSearchContainer").hide();
        $(".collectionTabNew").addClass("activeTab");
        $(".collectionTabSearch").removeClass("activeTab");
    });

    // run on page load
    $(".collectionNewContainer").hide();
    collectionList();
    //getCollections();

    $('.resource-container-level').click(function() {
       getCollections();
    });

});

//collections globals.
var collectionArray = [];
var isAnyChecked = 0;
var lastCheckedId = '';

//get collection list for search modal
function collectionList() {
    var pKid = $('#project1').attr('data-kid');
    collectionArray = [];
    $.ajax({
        url: arcs.baseURL + "collections/titlesAndIds",
        type: "get",
        data: {pKid: pKid},
        success: function (data) {
            data.forEach(function (tempdata) {
                var temparray = $.map(tempdata, function (value, index) {
                    return [value];
                });
                collectionArray.push(temparray);
            })
            collectionsSearch();
        }
    });
}

//unselect collections.
var unselect = function (trigger) {
    if (trigger == null) {
        trigger = true
    }
    this.$(".result").removeClass("selected");
    this.$(".select-button").removeClass("de-select");
    this.$(".select-button, #toggle-select").html("SELECT");
    this.$("#deselect-all").attr({id: "select-all"});
    this.$(".checkedboxes").prop("checked", false);
    this.$("#collectionTitle").val('');
    this.$(".collectionTabSearch").trigger("click");
    collectionList();
    checkSearchSubmitBtn();
}

// Hide/show add to collection button in collection modal
function checkSearchSubmitBtn() {
    // Hide add to collection button in collection modal when no collections are selected
    var checkboxes = $("#collectionSearchObjects > input");
    var submitButt = $(".collectionSearchSubmit");

    if (checkboxes.is(":checked")) {
        submitButt.show();
        isAnyChecked = 1;
    }
    else {
        submitButt.hide();
        isAnyChecked = 0;
    }
}

//update the collection search modal based on the new collection list.
function collectionsSearch() {
    var query = "";
    if ($(".collectionSearchBar").hasClass("first")) {
        query = "";
        $(".collectionSearchBar").removeClass("first");
    } else {
        query = $(".collectionSearchBar").val();
    }

    // only put collections in between the div if they include the query.
    // I.E. "" is in every collection title and user_name
    var populateCheckboxes = "
";
    for (var i = 0; i < collectionArray.length; i++) {
        if ((collectionArray[i][0].toLowerCase()).indexOf(query.toLowerCase()) != -1 ||
            (collectionArray[i][2].toLowerCase()).indexOf(query.toLowerCase()) != -1) {

            populateCheckboxes += "<input type='checkbox' class='checkedboxes' name='item-" + i + "' id='item-" + i + "' value='" + collectionArray[i][1] + "' />"
                + "
" + collectionArray[i][0] + "

" + collectionArray[i][2] + "

";
        }
    }
    $("#collectionSearchObjects").html(populateCheckboxes);

    var checkboxes = $("#collectionSearchObjects > input");
    checkboxes.click(function () {
        if (isAnyChecked == 1) {
            $('#' + lastCheckedId).prop("checked", false);
        }
        lastCheckedId = $(this).attr('id');
        checkSearchSubmitBtn();
    });
    $('#collectionTitle').bind('input propertychange', function () {
        if (this.value != "") {
            $(".collectionNewSubmit").show();
        } else {
            $(".collectionNewSubmit").hide();
        }
    });
}

//for the details tab, what collections the resource is a part of.
function getCollections() {
    /////********* Rix - Accordion fix is here*****
    ////////////////Yours is broken on version 517 now.
    var resource_kid = $('.selectedResource').prev();
    resource_kid = $(resource_kid).attr('id');
    resource_kid = resource_kid.replace('identifier-', '');
    //****************** End fix! ********

    var ctab = $("#collections_tab");
    ctab[0].innerHTML = "COLLECTIONS (" + 0 + ")";
    $("#collections_table").html('');
    $.ajax({
        url: arcs.baseURL + "collections/memberships",
        type: "get",
        data: {
            id: resource_kid
        },
        success: function (data) {
            var numCollections = 0;
            numCollections = data.collections.length;
            if (data.collections.length > 0) {
                var populateCollections = "

" +
                    "
This resource is a part of the following " + numCollections + " collections...
";
                for (var i = numCollections - 1; i >= 0; i--) {
                    var collection = data.collections[i];
                    populateCollections += "
" + collection.title + "	" + collection.user_name + "
";
                }
                populateCollections += "

";
            } else {
                var populateCollections = "

" +
                    "
This resource isn't part of any collections...
";
            }
            ctab[0].innerHTML = "COLLECTIONS (" + numCollections + ")";
            $("#collections_table").html(populateCollections);
        }
    })
}<|endoftext|>


<commit_before>// namespace
var MPLAY = MPLAY || {};

(function() {
	"use_strict";

	/**
	 *
	 * @class Page0
	 * @augments MPlay.MPlayPage
	 */
	var Page0 = function() {
		MPLAY.MPlayPage.call(this);
	};

	Page0.prototype = Object.create(MPLAY.MPlayPage.prototype);
	Page0.prototype.constructor = Page0;

	/**
	 * @override
	 */
	Page0.prototype._onLoad = function() {
		MPLAY.MPlayPage.prototype._onLoad.call(this);
		this._state = 0
		this._parentPosX = 0;

		this.setBackground("/static/gnovel/res/textures/backgrounds/enviroment concept.jpg");

		//create images
		this._professorImg = this.createImage("/static/gnovel/res/textures/char/prof sweeney- thoughtful.png", new THREE.Vector3(75, -130, 180), 600, 750);
		this._juliImg = this.createImage("/static/gnovel/res/textures/char/thoughtful-julia.png", new THREE.Vector3(-300, -140, 120), 600, 750);
		this._ryanImg = this.createImage("/static/gnovel/res/textures/char/ryan-happy.png", new THREE.Vector3(0, -80, 140), 600, 750);
		this._catImg = this.createImage("/static/gnovel/res/textures/char/cat-annoyed.png", new THREE.Vector3(450, -130, 100), 600, 750);

		this._talked = 0;

		var pageObj = this;
		var io1 = this.createInteractableObject(
			"/static/gnovel/res/textures/Perspective-Button-Stop-icon.png",
			{x: -100, y: 200, width : 64, height : 64, onClick: function() {
				pageObj._talked = 1;
				pageObj._runFlow();
			}});

		var io2 = this.createInteractableObject(
			"/static/gnovel/res/textures/Perspective-Button-Stop-icon.png",
			{x: 100, y: 200, width : 64, height : 64, onClick: function() {
				pageObj._talked = 2;
				pageObj._runFlow();
			}});

		this._professorImg.material.opacity = 0;
		this._juliImg.material.opacity = 0;
		this._ryanImg.material.opacity = 0;
		this._catImg.material.opacity = 0;

		this._professor = "professor";
		this._juli = "juli";
		this._ryan = "ryan";
		this._cat = "cat";

		// add object tags
		this._setObjectTag(this._professor, this._professorImg);
		this._setObjectTag(this._juli, this._juliImg);
		this._setObjectTag(this._ryan, this._ryanImg);
		this._setObjectTag(this._cat, this._catImg);
	};

	Page0.prototype._createFlowElements = function() {
		var professor = "%professor";
		var juli = "%juli";
		var ryan = "%ryan";
		var cat = "%cat";
		var o = null;

		if(this._talked == 1) {
			o = [
				{type: "show", img: ryan, waitUntilShown: false},
				{type: "dialog", speaker: "ryan", text: "hi, i'm ryan"},

				{type: "choices", choices :
					[{text: "good decision (+1 relationship)",
						go: "#relationshipDialogue",
						relationship: {name:"ryan", score:1}},
					{text : "bad decision(-1 relationship)",
						go : "#relationshipDialogue",
						relationship: {name:"ryan", score:-1}}]},

				{type: "dialog", speaker: "ryan",
					label: "relationshipDialogue",
					relationshipThreshold: 0,
					text: "you have a good relationship",
					expression: "",

					text2: "you have a bad relationship",
					expression2: ""},

				{type: "choices",
					choices :
						[{text: "good decision (+1 integrity)",
							go: "#integrityScore",
						integrityScore: 1},

						{text : "bad decision(-1 integrity)",
							go : "#integrityScore",
							integrityScore: -1}]},

				{type: "custom", label: "integrityScore", func: function(page) {
					return page._integrityManager.getIntegrity();
				}},

				{type: "compare", leftop: "$integrityScore", operator: "greater equal", rightop: 0, goTrue: "#good", goFalse: "#bad"},
				{type: "dialog", speaker: "ryan", text: "you have good ending", label: "good"},
				{type: "jump", condition: true, goTrue: 1000, goFalse: 1000},
				{type: "dialog", speaker: "ryan", text: "you have bad ending", label: "bad"},
				{type: "goto", page: 1},
			];
		}
		else if(this._talked == 2) {
			o = [
				{type: "show", img: cat},
				{type: "dialog", speaker: "cat", text: "hi, i'm cat", label : "choice_number_1"},
				{type: "hide", img: cat},
			];
		}

		return o;
	};

	MPLAY.Page0 = Page0;
}());<commit_msg>integrated scene 1, need more adjustments<commit_after>// namespace
var MPLAY = MPLAY || {};

(function() {
	"use_strict";

	/**
	 *
	 * @class Page0
	 * @augments MPlay.MPlayPage
	 */
	var Page0 = function() {
		MPLAY.MPlayPage.call(this);
	};

	Page0.prototype = Object.create(MPLAY.MPlayPage.prototype);
	Page0.prototype.constructor = Page0;

	/**
	 * @override
	 */
	Page0.prototype._onLoad = function() {
		MPLAY.MPlayPage.prototype._onLoad.call(this);
		this._state = 0
		this._parentPosX = 0;

		this.setBackground("/static/gnovel/res/textures/backgrounds/enviroment concept.jpg");

		//create images
		this._ryanImg = this.createImage("/static/gnovel/res/textures/char/ryan-happy.png", new THREE.Vector3(0, -80, 140), 600, 750);
		this._yourphoneImg = this.createImage("/static/gnovel/res/textures/phone.png", new THREE.Vector3(0, 60, 20), 250, 458);
		this._closephoneImg = this.createImage("/static/gnovel/res/textures/phone.png", new THREE.Vector3(0, 60, 160), 519, 950);
		this._catsphoneImg = this.createImage("/static/gnovel/res/textures/cat's phone.png", new THREE.Vector3(480, 0, 40), 100, 183);

		this._ryanImg.material.opacity = 0;
		this._yourphoneImg.material.opacity = 0;
		this._catsphoneImg.material.opacity = 0;
		this._closephoneImg.material.opacity = 0;

		this._ryan = "ryan";
		this._yourphone = "yourphone";
		this._catsphone = "catsphone";
		this._closephone = "closephone";

		// add object tags
		this._setObjectTag(this._ryan, this._ryanImg);
		this._setObjectTag(this._yourphone, this._yourphoneImg);
		this._setObjectTag(this._catsphone, this._catsphoneImg);
		this._setObjectTag(this._closephone, this._closephoneImg);
	};

	Page0.prototype._createFlowElements = function() {
		var ryan = "%ryan";
		var yourphone = "%yourphone";
		var catsphone = "%catsphone";
		var closephone = "%closephone";
		var player = "yourname";
		var o = null;

		o = [
			// need a flow here to show a buzzing phone before choices
			{type: "show", img: yourphone},
			{type: "choices",
				choices :
					[{text: "Look at your Phone ",
						go: "#lookatphone"},
					{text: "Talk to Ryan First",
						go: "#talktoryan"}]},
			// need a flow here to show the phone screen before next flow, and this flow should be labeled "lookatphone"
			{type: "hide", img: yourphone, waitUntilHiden: false, label: "lookatphone"},
			{type: "show", img: closephone},
			{type: "dialog", speaker: "Your phone", text: "Dear " + player + ", Glad you'll be joining us at the company.  Ryan was right - you'll make a great addition to the team.  We'll be in touch. -J. WANG"},
			{type: "hide", img: closephone, waitUntilHiden: false},
			{type: "show", img: ryan, position: "center", waitUntilShown: false},
			{type: "dialog", speaker: "ryan", text: "Congratulations! I am so happy you got the job - referring you was a good call.  We can continue the magic as cubicle neighbors after graduation."},
			{type: "choices",
				choices :
					[{text: "Yeah!  Thanks again for forwarding my resume.",
						go: "#cheers"},
					{text : "Psyched to be working with you after graduation, Ryan!",
						go: "#cheers"}]},
			{type: "dialog", speaker: "ryan", text: "Cheers!  Congratulations!", label: "cheers"},
			{type: "jump", condition: true, goTrue: "#timefade", goFalse: 1000},

			{type: "hide", img: yourphone, waitUntilHiden: false, label: "talktoryan"},
			{type: "show", img: ryan, position: "center", waitUntilShown: false},
			{type: "dialog", speaker: "ryan", text: "Check your phone, check your phone!"},
			{type: "choices",
				choices :
					[{text: "What?! Just tell me!",
						go: "#gotjob"},
					{text : "Good news?!",
						go: "#gotjob"}]},
			{type: "dialog", speaker: "ryan", text: "You got the job!  We’re going to be working together after graduation! You’ll love our boss.  He was so great during the internship.  I just know it’ll be great.", label: "gotjob"},
			{type: "hide", img: ryan},

			// after transition
			{type: "show", img: ryan, position: "center", waitUntilShown: false, label: "timefade"},
			{type: "dialog", speaker: "ryan", text: "Oh man, this semester is gonna be tough.  I think our class - Programming and Society should be good though.  My brother took it last year."},
			{type: "choices",
				choices :
					[{text: "Should be good.",
						go: "#mentionpriya"},
					{text : "Glad we’re in it together.",
						go: "#mentionpriya"}]},
			{type: "dialog", speaker: "ryan", text: "I think it's cross listed CS and psych or something.  My friend Priya is in it too.  She's really nice, I’ll introduce you guys.", label: "mentionpriya"},
			// see a phone on the table.
			{type: "show", img: catsphone},

			{type: "dialog", speaker: "ryan", text: "Looks like someone left their phone."},
			// this choice affects scene 2
			{type: "choices",
				choices :
					[{text: "That sucks for them.",
						go: "#sucks"},
					{text: "Pick up the phone to see if there’s any contact information.",
						go: "#pickupphone"},
					{text: "Let’s sell it on Ebay!",
						go: "#sellit"}]},

			{type: "dialog", speaker: "ryan", text: "So anyway, congrats again.  Better keep up that GPA – our boss warned me before I left last summer to keep it above a 3.5.", label: "sucks"},
			{type: "jump", condition: true, goTrue: "#hidephone", goFalse: 1000},

			{type: "dialog", speaker: "ryan", text: "Oh no.  Looks like it’s dead.  Sucks to be that guy.", label: "pickupphone"},
			{type: "choices",
				choices :
					[{text: "Let’s give it to the bartender to hold on to."},
					{text : "I’ll bring it home to charge.  Maybe the owner will contact it."}]},
			{type: "dialog", speaker: "ryan", text: "That should score you some Karma points"},
			{type: "jump", condition: true, goTrue: "#hidephone", goFalse: 1000},

			{type: "dialog", speaker: "ryan", text: "Ha!  I didn’t know you were so mean.  Leave it, I guess.", label: "sellit"},
			{type: "hide", img: catsphone, label: "hidephone"},

		];

		return o;
	};

	MPLAY.Page0 = Page0;
}());<|endoftext|>


<commit_before>/* Copyright 2017 The BioBricks Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var AJV = require('ajv')
var attachmentPath = require('./util/attachment-path')
var concat = require('concat-stream')
var ecb = require('ecb')
var encoding = require('./encoding')
var flushWriteStream = require('flush-write-stream')
var fs = require('fs')
var http = require('http-https')
var latest = require('./latest')
var mkdirp = require('mkdirp')
var parse = require('json-parse-errback')
var path = require('path')
var pump = require('pump')
var pumpify = require('pumpify')
var readRecord = require('./util/read-record')
var recordDirectoryPath = require('./util/record-directory-path')
var recordPath = require('./util/record-path')
var runParallel = require('run-parallel')
var runSeries = require('run-series')
var signaturePath = require('./util/timestamp-path')
var sodium = require('sodium-prebuilt').api
var split2 = require('split2')
var stringify = require('json-stable-stringify')
var through2 = require('through2')
var timestampPath = require('./util/timestamp-path')
var url = require('url')
var xtend = require('xtend')

var validatePublication = new AJV({allErrors: true})
  .compile(latest(require('./schemas/publication')))

var validateTimestamp = new AJV({allErrors: true})
  .compile(latest(require('./schemas/timestamp')))

module.exports = function (configuration, log) {
  var directory = configuration.directory
  readPeers(directory, ecb(logError, function (peers) {
    log.info({
      peers: peers.map(function (peer) {
        return url.format(peer.url)
      })
    }, 'read peers')
    runParallel(
      peers.map(function (peer) {
        return function (done) {
          var peerLog = log.child({peer: peer.url.hostname})
          peerLog.info('replicating')
          replicatePeer(configuration, peerLog, peer, done)
        }
      }),
      ecb(logError, function () {
        if (peers.length === 0) {
          log.info('done')
        } else {
          writePeers(directory, peers, ecb(logError, function () {
            log.info('done')
          }))
        }
      })
    )
  }))

  function logError (error) {
    log.error(error)
  }
}

function replicatePeer (configuration, log, peer, done) {
  var request = xtend(peer.url, {
    path: peer.url.path + '/accessions?from=' + (peer.last + 1),
    headers: {accept: 'text/csv'}
  })
  http.request(request)
    .once('error', function (error) {
      done(error)
    })
    .once('response', function (response) {
      var counter = peer.last
      pump(
        response,
        split2(),
        through2.obj(function (line, _, done) {
          counter++
          done(null, {
            accessionNumber: counter,
            digest: line.toString().split(',')[1]
          })
        }),
        flushWriteStream.obj(function (publication, _, done) {
          var recordLog = log.child({digest: publication.digest})
          republish(configuration, recordLog, peer, publication, done)
        }),
        function (error) {
          if (error) {
            done(error)
          } else {
            done()
          }
        }
      )
    })
    .end()
}

function republish (configuration, log, peer, publication, done) {
  var files = []
  var haveRecord
  var havePeerTimestamp
  var record
  var peerTimestamp
  runSeries([
    runParallel.bind(null, [
      checkHaveRecord,
      checkHavePeerTimestamp
    ]),
    runParallel.bind(null, [
      getRecord,
      unless(havePeerTimestamp, getPeerTimestamp)
    ]),
    unless(haveRecord, validateRecord),
    unless(havePeerTimestamp, validatePeerTimestamp),
    unless(haveRecord, makeDirectory),
    runParallel.bind(null, [
      unless(haveRecord, saveOwnTimestamp),
      unless(havePeerTimestamp, savePeerTimestamp),
      unless(haveRecord, saveRecord),
      unless(haveRecord, getAndSaveAttachments)
    ]),
    unless(haveRecord, appendToAccessions),
    bumpAccessionNumber
  ], function (error) {
    if (error) {
      log.error(error)
      unlinkFiles(done)
    } else {
      log.info('done')
      done()
    }
  })

  function unless (flag, step) {
    return function (done) {
      if (flag) {
        done()
      } else {
        step(done)
      }
    }
  }

  function checkHaveRecord (done) {
    var file = recordPath(configuration.directory, publication.digest)
    fileExists(file, ecb(done, function (exists) {
      haveRecord = exists
      log.info({haveRecord: exists})
      done()
    }))
  }

  function checkHavePeerTimestamp (done) {
    var file = timestampPath(
      configuration.directory,
      publication.digest,
      peer.publicKey
    )
    fileExists(file, ecb(done, function (exists) {
      havePeerTimestamp = exists
      log.info({havePeerTimestamp: exists})
      done()
    }))
  }

  function getRecord (done) {
    if (haveRecord) {
      readRecord(
        configuration.directory, publication.digest,
        ecb(done, function (parsed) {
          record = parsed
          done()
        })
      )
    } else {
      http.request(xtend(peer.url, {
        path: peer.url.path + '/publications/' + publication.digest,
        headers: {accept: 'application/json'}
      }))
        .once('error', function (error) {
          done(error)
        })
        .once('response', function (response) {
          pump(
            response,
            concat(function (buffer) {
              parse(buffer, ecb(done, function (parsed) {
                record = parsed
                log.info('got record')
                done()
              }))
            }),
            function (error) {
              if (error) {
                done(error)
              }
            }
          )
        })
        .end()
    }
  }

  function getPeerTimestamp (done) {
    var request = xtend(peer.url, {
      path: (
        peer.url.path +
        'publications/' + publication.digest +
        '/timestamps/' + encoding.encode(peer.publicKey)
      )
    })
    http.request(request)
      .once('error', function (error) {
        done(error)
      })
      .once('response', function (response) {
        if (response.statusCode !== 200) {
          var error = new Error('Could not get timestamp')
          error.statusCode = response.statusCode
          log.error({
            statusCode: response.statusCode
          }, 'failed to get timestamp')
          done(error)
        } else {
          pump(
            response,
            concat(function (buffer) {
              parse(buffer, ecb(done, function (parsed) {
                peerTimestamp = parsed
                log.info('got timestamp')
                done()
              }))
            }),
            function (error) {
              if (error) {
                done(error)
              }
            }
          )
        }
      })
      .end()
  }

  function validateRecord (done) {
    validatePublication(record)
    var errors = validatePublication.errors
    if (errors) {
      log.info({errors: errors}, 'invalid publication')
      var error = new Error('invalid record')
      error.validationErrors = errors
      return done(error)
    }
    var computedDigest = encoding.encode(
      sodium.crypto_hash_sha256(
        Buffer.from(stringify(record), 'utf8')
      )
    )
    if (computedDigest !== publication.digest) {
      done(new Error(
        'reported and computed digests do not match'
      ))
    }
    log.info('validated record')
    done()
  }

  function validatePeerTimestamp (done) {
    validateTimestamp(peerTimestamp.timestamp)
    var errors = validateTimestamp.errors
    if (errors) {
      log.info({errors: errors}, 'invalid timestamp')
      var error = new Error('invalid timestamp')
      error.validationErrors = errors
      return done(error)
    }
    var keys = Object.keys(peerTimestamp)
    if (keys.length !== 2 || !keys.includes('signature')) {
      log.info({keys: keys}, 'invalid timestamp record')
      var error = new Error('invalid timestamp record')
      return done(error)
    }
    var digestsMatch = (
      peerTimestamp.timestamp.digest ===
      encoding.encode(
        sodium.crypto_hash_sha256(
          Buffer.from(stringify(record), 'utf8')
        )
      )
    )
    if (!digestsMatch) {
      log.error('timestamp digest mismatch')
      return done(new Error(
        'peer timestamp digest does not match record'
      ))
    }
    var validSignature = sodium.crypto_sign_verify_detached(
      encoding.decode(peerTimestamp.signature),
      Buffer.from(stringify(peerTimestamp.timestamp)),
      peer.publicKey
    )
    if (!validSignature) {
      log.error('invalid peer signature')
      return done(new Error('invalid peer signature'))
    }
    log.info('validated timestamp')
    done()
  }

  function makeDirectory (done) {
    mkdirp(
      recordDirectoryPath(configuration.directory, publication.digest),
      done
    )
  }

  function saveOwnTimestamp (done) {
    time = new Date().toISOString()
    var timestamp = {
      digest: publication,
      uri: (
        'https://' + configuration.hostname +
        '/publications/' + publication.digest
      ),
      time: time
    }
    var signature = encoding.encode(
      sodium.crypto_sign_detached(
        Buffer.from(stringify(timestamp)),
        configuration.keypair.secret
      )
    )
    var file = signaturePath(
      configuration.directory,
      publication.digest,
      encoding.encode(configuration.keypair.public)
    )
    files.push(file)
    var data = stringify({
      timestamp: timestamp,
      signature: signature,
      version: timestampSchema.properties.version.constant
    })
    fs.writeFile(file, data, 'utf8', ecb(done, function () {
      log.info('saved own timestamp')
      done()
    }))
  }

  function savePeerTimestamp (done) {
    var file = timestampPath(
      configuration.directory,
      publication.digest,
      peer.publicKey
    )
    files.push(file)
    var data = stringify(peerTimestamp)
    fs.writeFile(file, data, 'utf8', ecb(done, function () {
      log.info('saved peer timestamp')
      done()
    }))
  }

  function saveRecord (done) {
    var file = recordPath(configuration.directory, publication.digest)
    files.push(file)
    var data = stringify(record)
    writeIfMissing(file, data, ecb(done, function () {
      log.info('saved record')
      done()
    }))
  }

  function getAndSaveAttachments (done) {
    runParallel(
      record.attachments.map(function (attachmentDigest) {
        return function (done) {
          var attachmentLog = log.child({
            attachment: attachmentDigest
          })
          var request = xtend(peer.url, {
            path: (
              peer.url.path +
              '/publications/' + publication.digest +
              '/attachments/' + attachmentDigest
            )
          })
          var file = attachmentPath(
            configuration.directory,
            publication.digest,
            attachmentDigest
          )
          download(request, file, ecb(done, function (contentType) {
            attachmentLog.info('downloaded')
            writeIfMissing(
              file + '.type', contentType,
              ecb(done, function () {
                attachmentLog.info('wrote type file')
                done()
              })
            )
          }))
        }
      }),
      done
    )
  }

  function appendToAccessions (done) {
    fs.appendFile(
      path.join(configuration.directory, 'accessions'),
      (time ? time : new Date().toISOString()) + ','
      + publication.digest + '\n',
      done
    )
  }

  function bumpAccessionNumber (done) {
    peer.last = publication.accessionNumber
    log.info('bumped')
    done()
  }

  function unlinkFiles (done) {
    runParallel(files.map(function (file) {
      return function (done) {
        var fileLog = log.child({file: file})
        fs.unlink(file, function (error) {
          if (error) {
            fileLog.error(error)
            done()
          } else {
            fileLog.info('unlinked')
            done()
          }
        })
      }
    }), done)
  }
}

function writeIfMissing (path, data, done) {
  fs.writeFile(path, data, {flag: 'wx'}, function (error) {
    if (error && error.code !== 'EEXIST') {
      done(error)
    } else {
      done()
    }
  })
}

function download (from, to, done) {
  var contentType
  http.request(from)
    .once('error', function (error) {
      done(error)
    })
    .once('response', function (response) {
      contentType = response.headers['Content-Type']
      pump(
        response,
        fs.createWriteStream(to, {flags: 'wx'}),
        function (error) {
          if (error) {
            if (error.code === 'EEXIST') {
              done()
            } else {
              done(error)
            }
          } else {
            done(null, contentType)
          }
        }
      )
    })
    .end()
}

function readPeers (directory, callback) {
  var file = path.join(directory, 'peers')
  fs.readFile(file, 'utf8', function (error, string) {
    if (error) {
      if (error.code === 'ENOENT') {
        callback(null, [])
      } else {
        callback(error)
      }
    } else {
      var peers = string
        .split('\n')
        .reduce(function (peers, line, index) {
          var split = line.split(',')
          if (split.length === 3) {
            try {
              var peer = {
                url: url.parse(split[0]),
                publicKey: encoding.decode(split[1]),
                last: parseInt(split[2])
              }
              peers.push(peer)
            } catch (error) {
              log.error('peers parse error', {
                line: line,
                number: index + 1
              })
            }
          }
          return peers
        }, [])
      callback(null, peers)
    }
  })
}

function writePeers (directory, peers, callback) {
  var file = path.join(directory, 'peers')
  var data = peers
    .map(function (peer) {
      return (
        url.format(peer.url) + ',' +
        encoding.encode(peer.publicKey) + ',' +
        peer.last.toString()
      )
    })
    .join('\n')
  fs.writeFile(file, data, callback)
}

function fileExists (path, callback) {
  fs.access(path, fs.constants.R_OK, function (error) {
    if (error) {
      if (error.code === 'ENOENT') {
        callback(null, false)
      } else {
        callback(error)
      }
    } else {
      callback(null, true)
    }
  })
}
<commit_msg>Fix replication code<commit_after>/* Copyright 2017 The BioBricks Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var AJV = require('ajv')
var attachmentPath = require('./util/attachment-path')
var concat = require('concat-stream')
var ecb = require('ecb')
var encoding = require('./encoding')
var flushWriteStream = require('flush-write-stream')
var fs = require('fs')
var http = require('http-https')
var latest = require('./latest')
var mkdirp = require('mkdirp')
var parse = require('json-parse-errback')
var path = require('path')
var pump = require('pump')
var readRecord = require('./util/read-record')
var recordDirectoryPath = require('./util/record-directory-path')
var recordPath = require('./util/record-path')
var runParallel = require('run-parallel')
var runSeries = require('run-series')
var signaturePath = require('./util/timestamp-path')
var sodium = require('sodium-prebuilt').api
var split2 = require('split2')
var stringify = require('json-stable-stringify')
var through2 = require('through2')
var timestampPath = require('./util/timestamp-path')
var url = require('url')
var xtend = require('xtend')

var timestampSchema = latest(require('./schemas/timestamp'))

var validatePublication = new AJV({allErrors: true})
  .compile(latest(require('./schemas/publication')))

var validateTimestamp = new AJV({allErrors: true})
  .compile(timestampSchema)

module.exports = function (configuration, log) {
  var directory = configuration.directory
  readPeers(directory, log, ecb(logError, function (peers) {
    log.info({
      peers: peers.map(function (peer) {
        return url.format(peer.url)
      })
    }, 'read peers')
    runParallel(
      peers.map(function (peer) {
        return function (done) {
          var peerLog = log.child({peer: peer.url.hostname})
          peerLog.info('replicating')
          replicatePeer(configuration, peerLog, peer, done)
        }
      }),
      ecb(logError, function () {
        if (peers.length === 0) {
          log.info('done')
        } else {
          writePeers(directory, peers, ecb(logError, function () {
            log.info('done')
          }))
        }
      })
    )
  }))

  function logError (error) {
    log.error(error)
  }
}

function replicatePeer (configuration, log, peer, done) {
  var request = xtend(peer.url, {
    path: peer.url.path + '/accessions?from=' + (peer.last + 1),
    headers: {accept: 'text/csv'}
  })
  http.request(request)
    .once('error', function (error) {
      done(error)
    })
    .once('response', function (response) {
      var counter = peer.last
      pump(
        response,
        split2(),
        through2.obj(function (line, _, done) {
          counter++
          done(null, {
            accessionNumber: counter,
            digest: line.toString().split(',')[1]
          })
        }),
        flushWriteStream.obj(function (publication, _, done) {
          var recordLog = log.child({digest: publication.digest})
          republish(configuration, recordLog, peer, publication, done)
        }),
        function (error) {
          if (error) {
            done(error)
          } else {
            done()
          }
        }
      )
    })
    .end()
}

function republish (configuration, log, peer, publication, done) {
  var time = new Date().toISOString()
  var files = []
  var haveRecord
  var havePeerTimestamp
  var record
  var peerTimestamp
  runSeries([
    runParallel.bind(null, [
      checkHaveRecord,
      checkHavePeerTimestamp
    ]),
    runParallel.bind(null, [
      getRecord,
      unless(havePeerTimestamp, getPeerTimestamp)
    ]),
    unless(haveRecord, validateRecord),
    unless(havePeerTimestamp, validatePeerTimestamp),
    unless(haveRecord, makeDirectory),
    runParallel.bind(null, [
      unless(haveRecord, saveOwnTimestamp),
      unless(havePeerTimestamp, savePeerTimestamp),
      unless(haveRecord, saveRecord),
      unless(haveRecord, getAndSaveAttachments)
    ]),
    unless(haveRecord, appendToAccessions),
    bumpAccessionNumber
  ], function (error) {
    if (error) {
      log.error(error)
      unlinkFiles(done)
    } else {
      log.info('done')
      done()
    }
  })

  function unless (flag, step) {
    return function (done) {
      if (flag) {
        done()
      } else {
        step(done)
      }
    }
  }

  function checkHaveRecord (done) {
    var file = recordPath(configuration.directory, publication.digest)
    fileExists(file, ecb(done, function (exists) {
      haveRecord = exists
      log.info({haveRecord: exists})
      done()
    }))
  }

  function checkHavePeerTimestamp (done) {
    var file = timestampPath(
      configuration.directory,
      publication.digest,
      peer.publicKey
    )
    fileExists(file, ecb(done, function (exists) {
      havePeerTimestamp = exists
      log.info({havePeerTimestamp: exists})
      done()
    }))
  }

  function getRecord (done) {
    if (haveRecord) {
      readRecord(
        configuration.directory, publication.digest,
        ecb(done, function (parsed) {
          record = parsed
          done()
        })
      )
    } else {
      http.request(xtend(peer.url, {
        path: peer.url.path + '/publications/' + publication.digest,
        headers: {accept: 'application/json'}
      }))
        .once('error', function (error) {
          done(error)
        })
        .once('response', function (response) {
          pump(
            response,
            concat(function (buffer) {
              parse(buffer, ecb(done, function (parsed) {
                record = parsed
                log.info('got record')
                done()
              }))
            }),
            function (error) {
              if (error) {
                done(error)
              }
            }
          )
        })
        .end()
    }
  }

  function getPeerTimestamp (done) {
    var request = xtend(peer.url, {
      path: (
        peer.url.path +
        'publications/' + publication.digest +
        '/timestamps/' + encoding.encode(peer.publicKey)
      )
    })
    http.request(request)
      .once('error', function (error) {
        done(error)
      })
      .once('response', function (response) {
        if (response.statusCode !== 200) {
          var error = new Error('Could not get timestamp')
          error.statusCode = response.statusCode
          log.error({
            statusCode: response.statusCode
          }, 'failed to get timestamp')
          done(error)
        } else {
          pump(
            response,
            concat(function (buffer) {
              parse(buffer, ecb(done, function (parsed) {
                peerTimestamp = parsed
                log.info('got timestamp')
                done()
              }))
            }),
            function (error) {
              if (error) {
                done(error)
              }
            }
          )
        }
      })
      .end()
  }

  function validateRecord (done) {
    validatePublication(record)
    var errors = validatePublication.errors
    if (errors) {
      log.info({errors: errors}, 'invalid publication')
      var error = new Error('invalid record')
      error.validationErrors = errors
      return done(error)
    }
    var computedDigest = encoding.encode(
      sodium.crypto_hash_sha256(
        Buffer.from(stringify(record), 'utf8')
      )
    )
    if (computedDigest !== publication.digest) {
      done(new Error(
        'reported and computed digests do not match'
      ))
    }
    log.info('validated record')
    done()
  }

  function validatePeerTimestamp (done) {
    validateTimestamp(peerTimestamp)
    var errors = validateTimestamp.errors
    if (errors) {
      log.info({errors: errors}, 'invalid timestamp')
      var error = new Error('invalid timestamp')
      error.validationErrors = errors
      return done(error)
    }
    var digestsMatch = (
      peerTimestamp.timestamp.digest ===
      encoding.encode(
        sodium.crypto_hash_sha256(
          Buffer.from(stringify(record), 'utf8')
        )
      )
    )
    if (!digestsMatch) {
      log.error('timestamp digest mismatch')
      return done(new Error(
        'peer timestamp digest does not match record'
      ))
    }
    var validSignature = sodium.crypto_sign_verify_detached(
      encoding.decode(peerTimestamp.signature),
      Buffer.from(stringify(peerTimestamp.timestamp)),
      peer.publicKey
    )
    if (!validSignature) {
      log.error('invalid peer signature')
      return done(new Error('invalid peer signature'))
    }
    log.info('validated timestamp')
    done()
  }

  function makeDirectory (done) {
    mkdirp(
      recordDirectoryPath(configuration.directory, publication.digest),
      done
    )
  }

  function saveOwnTimestamp (done) {
    var timestamp = {
      digest: publication,
      uri: (
        'https://' + configuration.hostname +
        '/publications/' + publication.digest
      ),
      time: time
    }
    var signature = encoding.encode(
      sodium.crypto_sign_detached(
        Buffer.from(stringify(timestamp)),
        configuration.keypair.secret
      )
    )
    var file = signaturePath(
      configuration.directory,
      publication.digest,
      encoding.encode(configuration.keypair.public)
    )
    files.push(file)
    var data = stringify({
      timestamp: timestamp,
      signature: signature,
      version: timestampSchema.properties.version.constant
    })
    fs.writeFile(file, data, 'utf8', ecb(done, function () {
      log.info('saved own timestamp')
      done()
    }))
  }

  function savePeerTimestamp (done) {
    var file = timestampPath(
      configuration.directory,
      publication.digest,
      peer.publicKey
    )
    files.push(file)
    var data = stringify(peerTimestamp)
    fs.writeFile(file, data, 'utf8', ecb(done, function () {
      log.info('saved peer timestamp')
      done()
    }))
  }

  function saveRecord (done) {
    var file = recordPath(configuration.directory, publication.digest)
    files.push(file)
    var data = stringify(record)
    writeIfMissing(file, data, ecb(done, function () {
      log.info('saved record')
      done()
    }))
  }

  function getAndSaveAttachments (done) {
    runParallel(
      record.attachments.map(function (attachmentDigest) {
        return function (done) {
          var attachmentLog = log.child({
            attachment: attachmentDigest
          })
          var request = xtend(peer.url, {
            path: (
              peer.url.path +
              '/publications/' + publication.digest +
              '/attachments/' + attachmentDigest
            )
          })
          var file = attachmentPath(
            configuration.directory,
            publication.digest,
            attachmentDigest
          )
          download(request, file, ecb(done, function (contentType) {
            attachmentLog.info('downloaded')
            writeIfMissing(
              file + '.type', contentType,
              ecb(done, function () {
                attachmentLog.info('wrote type file')
                done()
              })
            )
          }))
        }
      }),
      done
    )
  }

  function appendToAccessions (done) {
    fs.appendFile(
      path.join(configuration.directory, 'accessions'),
      (time || new Date().toISOString()) + ',' +
      publication.digest + '\n',
      done
    )
  }

  function bumpAccessionNumber (done) {
    peer.last = publication.accessionNumber
    log.info('bumped')
    done()
  }

  function unlinkFiles (done) {
    runParallel(files.map(function (file) {
      return function (done) {
        var fileLog = log.child({file: file})
        fs.unlink(file, function (error) {
          if (error) {
            fileLog.error(error)
            done()
          } else {
            fileLog.info('unlinked')
            done()
          }
        })
      }
    }), done)
  }
}

function writeIfMissing (path, data, done) {
  fs.writeFile(path, data, {flag: 'wx'}, function (error) {
    if (error && error.code !== 'EEXIST') {
      done(error)
    } else {
      done()
    }
  })
}

function download (from, to, done) {
  var contentType
  http.request(from)
    .once('error', function (error) {
      done(error)
    })
    .once('response', function (response) {
      contentType = response.headers['Content-Type']
      pump(
        response,
        fs.createWriteStream(to, {flags: 'wx'}),
        function (error) {
          if (error) {
            if (error.code === 'EEXIST') {
              done()
            } else {
              done(error)
            }
          } else {
            done(null, contentType)
          }
        }
      )
    })
    .end()
}

function readPeers (directory, log, callback) {
  var file = path.join(directory, 'peers')
  fs.readFile(file, 'utf8', function (error, string) {
    if (error) {
      if (error.code === 'ENOENT') {
        callback(null, [])
      } else {
        callback(error)
      }
    } else {
      var peers = string
        .split('\n')
        .reduce(function (peers, line, index) {
          var split = line.split(',')
          if (split.length === 3) {
            try {
              var peer = {
                url: url.parse(split[0]),
                publicKey: encoding.decode(split[1]),
                last: parseInt(split[2])
              }
              peers.push(peer)
            } catch (error) {
              log.error('peers parse error', {
                line: line,
                number: index + 1
              })
            }
          }
          return peers
        }, [])
      callback(null, peers)
    }
  })
}

function writePeers (directory, peers, callback) {
  var file = path.join(directory, 'peers')
  var data = peers
    .map(function (peer) {
      return (
        url.format(peer.url) + ',' +
        encoding.encode(peer.publicKey) + ',' +
        peer.last.toString()
      )
    })
    .join('\n')
  fs.writeFile(file, data, callback)
}

function fileExists (path, callback) {
  fs.access(path, fs.constants.R_OK, function (error) {
    if (error) {
      if (error.code === 'ENOENT') {
        callback(null, false)
      } else {
        callback(error)
      }
    } else {
      callback(null, true)
    }
  })
}
<|endoftext|>


<commit_before>/*
 *  local storage(web/react native) wrapper
 *  sunnylqm 2016-03-03
 *  version 0.0.16
 */

export default class Storage {
  constructor(options = {}) {
    let me = this;

    me._SIZE = options.size || 1000;   // maximum capacity
    me.sync = options.sync || {};      // remote sync method
    me.defaultExpires = options.defaultExpires || 1000 * 3600 * 24;
    me.enableCache = options.enableCache || true;
    me._s = options.storageBackend || null;
    me.isPromise = options.isPromise || true;
    me._innerVersion = 10;
    me.cache = {};

    if(!me._s) {
      if(typeof window !== 'undefined' && window.localStorage) {
        try {
          // avoid key conflict
          window.localStorage.setItem('__react_native_storage_test', 'test');

          me._s = window.localStorage;
          me.isPromise = false;
        }
        catch(e) {
          console.warn(e);
          delete me._s;
          throw e;
        }
      }
      else {
        me._s = require('react-native').AsyncStorage;
      }
    }

    me._mapPromise = me.getItem('map').then( map => {
      me._m = me._checkMap(map && JSON.parse(map) || {});
      // delete me._mapPromise;
    });
  }
  getItem(key) {
    return this._s
      ?
      this.isPromise ? this._s.getItem(key) : Promise.resolve(this._s.getItem(key))
      :
      Promise.resolve();
  }
  setItem(key, value) {
    return this._s
      ?
      this.isPromise ? this._s.setItem(key, value) : Promise.resolve(this._s.setItem(key, value))
      :
      Promise.resolve();
  }
  removeItem(key) {
    return this._s
      ? this.isPromise ? this._s.removeItem(key) : Promise.resolve(this._s.removeItem(key))
      :
      Promise.resolve();
  }
  _checkMap(map) {
    let me = this;
    if(map && map.innerVersion && map.innerVersion === me._innerVersion ) {
      return map;
    }
    else {
      return {
        innerVersion: me._innerVersion,
        index: 0
      };
    }
  }
  _getId(key, id) {
    return key + '_' + id;
  }
  _saveToMap(params) {
    let { key, id, data } = params,
      newId = this._getId(key, id),
      m = this._m;
    if(m[newId] !== undefined) {
      //update existed data
      if(this.enableCache) this.cache[newId] = JSON.parse(data);
      return this.setItem('map_' + m[newId], data);
    }
    if(m[m.index] !== undefined){
      //loop over, delete old data
      let oldId = m[m.index];
      delete m[oldId];
      if(this.enableCache) {
        delete this.cache[oldId];
      }
    }
    m[newId] = m.index;
    m[m.index] = newId;
    if(this.enableCache) {
      const cacheData = JSON.parse(data);
      this.cache[newId] = cacheData;
    }
    let currentIndex = m.index;
    if(++m.index === this._SIZE) {
      m.index = 0;
    }
    this.setItem('map_' + currentIndex, data);
    this.setItem('map', JSON.stringify(m));
  }
  save(params) {
    let me = this;
    let { key, id, rawData, expires } = params;
    if(key.toString().indexOf('_') !== -1) {
      console.error('Please do not use "_" in key!');
    }
    let data = {
      rawData
    };
    let now = new Date().getTime();
    if(expires === undefined) {
      expires = me.defaultExpires;
    }
    if(expires !== null) {
      data.expires = now + expires;
    }
    data = JSON.stringify(data);
    if(id === undefined) {
      if(me.enableCache) {
        const cacheData = JSON.parse(data);
        me.cache[key] = cacheData;
      }
      return me.setItem(key, data);
    }
    else {
      if(id.toString().indexOf('_') !== -1) {
        console.error('Please do not use "_" in id!');
      }
      return this._mapPromise.then(() => me._saveToMap({
        key,
        id,
        data
      }));
    }
  }
  getBatchData(querys) {
    let me = this;
    let tasks = [];
    for(let i = 0, query; query = querys[i]; i++) {
      tasks[i] = me.load(query);
    }
    return Promise.all(tasks);
  }
  getBatchDataWithIds(params) {
    let me = this;
    let { key, ids, syncInBackground } = params;

    return Promise.all(
      ids.map((id) => me.load({ key, id, syncInBackground, autoSync: false, batched: true }))
    ).then((results) => {
      return new Promise((resolve, reject) => {
        const ids = results.filter((value) => value.syncId !== undefined);
        if(!ids.length){
          return resolve();
        }
        return me.sync[key]({
          id: ids.map((value) => value.syncId),
          resolve,
          reject
        });
      }).then((data) => {
        return results.map(value => {
          return value.syncId ? data.shift() : value
        });
      });
    })
  }
  _lookupGlobalItem(params) {
    let me = this,
      ret;
    let { key } = params;
    if(me.enableCache && me.cache[key] !== undefined) {
      ret = me.cache[key];
      return me._loadGlobalItem({ ret, ...params });
    }
    return me.getItem(key).then(ret => me._loadGlobalItem({ ret, ...params }));
  }
  _loadGlobalItem(params) {
    let me = this;
    let { key, ret, autoSync, syncInBackground } = params;
    if(ret === null || ret === undefined) {
      if(autoSync && me.sync[key]) {
        return new Promise((resolve, reject) => me.sync[key]({resolve, reject}));
      }
      return Promise.reject();
    }
    if(typeof ret === 'string') {
      ret = JSON.parse(ret);
    }
    let now = new Date().getTime();
    if(autoSync && ret.expires < now && me.sync[key]) {
      if(syncInBackground) {
        me.sync[key]({});
        return Promise.resolve(ret.rawData);
      }
      return new Promise((resolve, reject) => me.sync[key]({resolve, reject}));
    }
    return Promise.resolve(ret.rawData);
  }
  _noItemFound(params) {
    let me = this;
    let { key, id, autoSync } = params;
    if(me.sync[key]) {
      if(autoSync) {
        return new Promise((resolve, reject) => me.sync[key]({id, resolve, reject}));
      }
      return Promise.resolve({ syncId: id });
    }
    return Promise.reject();
  }
  _loadMapItem(params) {
    let me = this;
    let { ret, key, id, autoSync, batched, syncInBackground } = params;
    if(ret === null || ret === undefined) {
      return me._noItemFound(params);
    }
    if(typeof ret === 'string'){
      ret = JSON.parse(ret);
    }
    let now = new Date().getTime();
    if(autoSync && ret.expires < now) {
      if(me.sync[key]) {
        if(syncInBackground){
          me.sync[key]({id});
          return Promise.resolve(ret.rawData);
        }
        return new Promise((resolve, reject) => me.sync[key]({id, resolve, reject}));
      }
      if(batched) {
        return Promise.resolve({ syncId: id });
      }
    }
    return Promise.resolve(ret.rawData);
  }
  _lookUpInMap(params) {
    let me = this,
      m = me._m,
      ret;
    let { key, id } = params;
    let newId = me._getId(key, id);
    if(me.enableCache && me.cache[newId]) {
      ret = me.cache[newId];
      return me._loadMapItem( {ret, ...params } );
    }
    if(m[newId] !== undefined) {
      return me.getItem('map_' + m[newId]).then( ret => me._loadMapItem( {ret, ...params } ) );
    }
    return me._noItemFound( {ret, ...params } );
  }
  remove(params) {
    return this._mapPromise.then(() => {
      let me = this,
        m = me._m;
      let { key, id } = params;

      if(id === undefined) {
        if(me.enableCache && me.cache[key]) {
          delete me.cache[key];
        }
        return me.removeItem(key);
      }
      let newId = me._getId(key, id);

      //remove existed data
      if(m[newId] !== undefined) {
        if(me.enableCache && me.cache[newId]) {
          delete me.cache[newId];
        }
        let idTobeDeleted = m[newId];
        delete m[newId];
        me.setItem('map', JSON.stringify(m));
        return me.removeItem('map_' + idTobeDeleted);
      }
    });
  }
  load(params) {
    let me = this;
    let { key, id, autoSync, syncInBackground } = params;
    if(autoSync === undefined) {
      autoSync = true;
    }
    if(syncInBackground === undefined) {
      syncInBackground = true;
    }
    return me._mapPromise.then(() => new Promise((resolve, reject) => {
      if(id === undefined) {
        return resolve(me._lookupGlobalItem({key, resolve, reject, autoSync, syncInBackground}));
      }
      return resolve(me._lookUpInMap({key, id, resolve, reject, autoSync, syncInBackground}));
    }));
  }
  clearMap(){
    let me = this;
    me.removeItem('map');
    me._m = {
      innerVersion: me._innerVersion,
      index: 0
    };
  }
}

// function noop() {}
//
// // compatible with legacy version promise
// function handlePromise (fn) {
//   return new Promise((resolve, reject) => {
//     var promise, isPromise;
//     if (isPromise = (promise = fn((data) => isPromise ? noop() : resolve(data), (err) => isPromise ? noop() : reject(err))) instanceof Promise) resolve(promise);
//   });
// }
<commit_msg>添加删除一个key下所有id对应的数据的功能<commit_after>/*
 *  local storage(web/react native) wrapper
 *  sunnylqm 2016-03-03
 *  version 0.0.16
 */

export default class Storage {
  constructor(options = {}) {
    let me = this;

    me._SIZE = options.size || 1000;   // maximum capacity
    me.sync = options.sync || {};      // remote sync method
    me.defaultExpires = options.defaultExpires || 1000 * 3600 * 24;
    me.enableCache = options.enableCache || true;
    me._s = options.storageBackend || null;
    me.isPromise = options.isPromise || true;
    me._innerVersion = 11;
    me.cache = {};

    if(!me._s) {
      if(window && window.localStorage) {
        try {
          // avoid key conflict
          window.localStorage.setItem('__react_native_storage_test', 'test');

          me._s = window.localStorage;
          me.isPromise = false;
        }
        catch(e) {
          console.warn(e);
          delete me._s;
          throw e;
        }
      }
      else {
        me._s = require('react-native').AsyncStorage;
      }
    }

    me._mapPromise = me.getItem('map').then( map => {
      me._m = me._checkMap(map && JSON.parse(map) || {});
      // delete me._mapPromise;
    });
  }
  getItem(key) {
    return this._s
      ?
      this.isPromise ? this._s.getItem(key) : Promise.resolve(this._s.getItem(key))
      :
      Promise.resolve();
  }
  setItem(key, value) {
    return this._s
      ?
      this.isPromise ? this._s.setItem(key, value) : Promise.resolve(this._s.setItem(key, value))
      :
      Promise.resolve();
  }
  removeItem(key) {
    return this._s
      ? this.isPromise ? this._s.removeItem(key) : Promise.resolve(this._s.removeItem(key))
      :
      Promise.resolve();
  }
  _checkMap(map) {
    let me = this;
    if(map && map.innerVersion && map.innerVersion === me._innerVersion ) {
      return map;
    }
    else {
      return {
        innerVersion: me._innerVersion,
        index: 0,
        __keys__: {}
      };
    }
  }
  _getId(key, id) {
    return key + '_' + id;
  }
  _saveToMap(params) {
    let { key, id, data } = params,
      newId = this._getId(key, id),
      m = this._m;
    if(m[newId] !== undefined) {
      //update existed data
      if(this.enableCache) this.cache[newId] = JSON.parse(data);
      return this.setItem('map_' + m[newId], data);
    }
    if(m[m.index] !== undefined){
      //loop over, delete old data
      let oldId = m[m.index];
      delete m[oldId];
      if(this.enableCache) {
        delete this.cache[oldId];
      }
    }
    m[newId] = m.index;
    m[m.index] = newId;

    m.__keys__[key] = m.__keys__[key] || [];
    m.__keys__[key].push(id);

    if(this.enableCache) {
      const cacheData = JSON.parse(data);
      this.cache[newId] = cacheData;
    }
    let currentIndex = m.index;
    if(++m.index === this._SIZE) {
      m.index = 0;
    }
    this.setItem('map_' + currentIndex, data);
    this.setItem('map', JSON.stringify(m));
  }
  save(params) {
    let me = this;
    let { key, id, rawData, expires } = params;
    if(key.toString().indexOf('_') !== -1) {
      console.error('Please do not use "_" in key!');
    }
    let data = {
      rawData
    };
    let now = new Date().getTime();
    if(expires === undefined) {
      expires = me.defaultExpires;
    }
    if(expires !== null) {
      data.expires = now + expires;
    }
    data = JSON.stringify(data);
    if(id === undefined) {
      if(me.enableCache) {
        const cacheData = JSON.parse(data);
        me.cache[key] = cacheData;
      }
      return me.setItem(key, data);
    }
    else {
      if(id.toString().indexOf('_') !== -1) {
        console.error('Please do not use "_" in id!');
      }
      return this._mapPromise.then(() => me._saveToMap({
        key,
        id,
        data
      }));
    }
  }
  getBatchData(querys) {
    let me = this;
    let tasks = [];
    for(let i = 0, query; query = querys[i]; i++) {
      tasks[i] = me.load(query);
    }
    return Promise.all(tasks);
  }
  getBatchDataWithIds(params) {
    let me = this;
    let { key, ids, syncInBackground } = params;

    return Promise.all(
      ids.map((id) => me.load({ key, id, syncInBackground, autoSync: false, batched: true }))
    ).then((results) => {
      return new Promise((resolve, reject) => {
        const ids = results.filter((value) => value.syncId !== undefined);
        if(!ids.length){
          return resolve();
        }
        return me.sync[key]({
          id: ids.map((value) => value.syncId),
          resolve,
          reject
        });
      }).then((data) => {
        return results.map(value => {
          return value.syncId ? data.shift() : value
        });
      });
    })
  }
  _lookupGlobalItem(params) {
    let me = this,
      ret;
    let { key } = params;
    if(me.enableCache && me.cache[key] !== undefined) {
      ret = me.cache[key];
      return me._loadGlobalItem({ ret, ...params });
    }
    return me.getItem(key).then(ret => me._loadGlobalItem({ ret, ...params }));
  }
  _loadGlobalItem(params) {
    let me = this;
    let { key, ret, autoSync, syncInBackground } = params;
    if(ret === null || ret === undefined) {
      if(autoSync && me.sync[key]) {
        return new Promise((resolve, reject) => me.sync[key]({resolve, reject}));
      }
      return Promise.reject();
    }
    if(typeof ret === 'string') {
      ret = JSON.parse(ret);
    }
    let now = new Date().getTime();
    if(autoSync && ret.expires < now && me.sync[key]) {
      if(syncInBackground) {
        me.sync[key]({});
        return Promise.resolve(ret.rawData);
      }
      return new Promise((resolve, reject) => me.sync[key]({resolve, reject}));
    }
    return Promise.resolve(ret.rawData);
  }
  _noItemFound(params) {
    let me = this;
    let { key, id, autoSync } = params;
    if(me.sync[key]) {
      if(autoSync) {
        return new Promise((resolve, reject) => me.sync[key]({id, resolve, reject}));
      }
      return Promise.resolve({ syncId: id });
    }
    return Promise.reject();
  }
  _loadMapItem(params) {
    let me = this;
    let { ret, key, id, autoSync, batched, syncInBackground } = params;
    if(ret === null || ret === undefined) {
      return me._noItemFound(params);
    }
    if(typeof ret === 'string'){
      ret = JSON.parse(ret);
    }
    let now = new Date().getTime();
    if(autoSync && ret.expires < now) {
      if(me.sync[key]) {
        if(syncInBackground){
          me.sync[key]({id});
          return Promise.resolve(ret.rawData);
        }
        return new Promise((resolve, reject) => me.sync[key]({id, resolve, reject}));
      }
      if(batched) {
        return Promise.resolve({ syncId: id });
      }
    }
    return Promise.resolve(ret.rawData);
  }
  _lookUpInMap(params) {
    let me = this,
      m = me._m,
      ret;
    let { key, id } = params;
    let newId = me._getId(key, id);
    if(me.enableCache && me.cache[newId]) {
      ret = me.cache[newId];
      return me._loadMapItem( {ret, ...params } );
    }
    if(m[newId] !== undefined) {
      return me.getItem('map_' + m[newId]).then( ret => me._loadMapItem( {ret, ...params } ) );
    }
    return me._noItemFound( {ret, ...params } );
  }
  remove(params) {
    return this._mapPromise.then(() => {
      let me = this,
        m = me._m;
      let { key, id } = params;

      if(id === undefined) {
        if(me.enableCache && me.cache[key]) {
          delete me.cache[key];
        }
        return me.removeItem(key);
      }
      let newId = me._getId(key, id);

      //remove existed data
      if(m[newId] !== undefined) {
        if(me.enableCache && me.cache[newId]) {
          delete me.cache[newId];
        }
        let idTobeDeleted = m[newId];
        delete m[newId];
        me.setItem('map', JSON.stringify(m));
        return me.removeItem('map_' + idTobeDeleted);
      }
    });
  }
  load(params) {
    let me = this;
    let { key, id, autoSync, syncInBackground } = params;
    if(autoSync === undefined) {
      autoSync = true;
    }
    if(syncInBackground === undefined) {
      syncInBackground = true;
    }
    return me._mapPromise.then(() => new Promise((resolve, reject) => {
      if(id === undefined) {
        return resolve(me._lookupGlobalItem({key, resolve, reject, autoSync, syncInBackground}));
      }
      return resolve(me._lookUpInMap({key, id, resolve, reject, autoSync, syncInBackground}));
    }));
  }
  clearMap(){
    let me = this;
    me.removeItem('map');
    me._m = {
      innerVersion: me._innerVersion,
      index: 0
    };
  }

  clearMapForKey(key) {
    let me = this;
    let m = me._m;
    m.__keys__[key].forEach(function(id){
      me.remove({ key: key, id: id });
    });
    m.__keys__[key] = []
  }
}

// function noop() {}
//
// // compatible with legacy version promise
// function handlePromise (fn) {
//   return new Promise((resolve, reject) => {
//     var promise, isPromise;
//     if (isPromise = (promise = fn((data) => isPromise ? noop() : resolve(data), (err) => isPromise ? noop() : reject(err))) instanceof Promise) resolve(promise);
//   });
// }
<|endoftext|>


<commit_before>// This file contains methods responsible for maintaining a TraversalContext.

import traverse from "../index";

export function call(key) {
  var node = this.node;
  if (!node) return;

  var opts = this.opts;

  for (var fns of [opts[key], opts[node.type] && opts[node.type][key]]){
    if (!fns) continue;

    for (var fn of (fns: Array)) {
      if (!fn) continue;

      let node = this.node;
      if (!node) return;

      var previousType = this.type;

      // call the function with the params (node, parent, scope, state)
      var replacement = fn.call(this, this, this.state);

      if (replacement) {
        this.replaceWith(replacement, true);
      }

      if (this.shouldStop || this.shouldSkip || this.removed) return;

      if (previousType !== this.type) {
        this.queueNode(this);
        return;
      }
    }
  }
}

export function isBlacklisted(): boolean {
  var blacklist = this.opts.blacklist;
  return blacklist && blacklist.indexOf(this.node.type) > -1;
}

export function visit(): boolean {
  if (this.isBlacklisted()) return false;
  if (this.opts.shouldSkip && this.opts.shouldSkip(this)) return false;

  this.call("enter");

  if (this.shouldSkip) {
    return this.shouldStop;
  }

  var node = this.node;
  var opts = this.opts;

  if (node) {
    if (Array.isArray(node)) {
      // traverse over these replacement nodes we purposely don't call exitNode
      // as the original node has been destroyed
      for (var i = 0; i < node.length; i++) {
        traverse.node(node[i], opts, this.scope, this.state, this, this.skipKeys);
      }
    } else {
      traverse.node(node, opts, this.scope, this.state, this, this.skipKeys);
      this.call("exit");
    }
  }

  return this.shouldStop;
}

export function skip() {
  this.shouldSkip = true;
}

export function skipKey(key) {
  this.skipKeys[key] = true;
}

export function stop() {
  this.shouldStop = true;
  this.shouldSkip = true;
}

export function setScope() {
  if (this.opts && this.opts.noScope) return;

  var target = this.context || this.parentPath;
  this.scope = this.getScope(target && target.scope);
  if (this.scope) this.scope.init();
}

export function setContext(context) {
  this.shouldSkip = false;
  this.shouldStop = false;
  this.removed    = false;
  this.skipKeys   = {};

  if (context) {
    this.context = context;
    this.state   = context.state;
    this.opts    = context.opts;
  }

  this.setScope();

  return this;
}

/**
 * Here we resync the node paths `key` and `container`. If they've changed according
 * to what we have stored internally then we attempt to resync by crawling and looking
 * for the new values.
 */

export function resync() {
  if (this.removed) return;

  this._resyncParent();
  this._resyncList();
  this._resyncKey();
  //this._resyncRemoved();
}

export function _resyncParent() {
  if (this.parentPath) {
    this.parent = this.parentPath.node;
  }
}

export function _resyncKey() {
  if (!this.container) return;

  if (this.node === this.container[this.key]) return;

  // grrr, path key is out of sync. this is likely due to a modification to the AST
  // not done through our path APIs

  if (Array.isArray(this.container)) {
    for (var i = 0; i < this.container.length; i++) {
      if (this.container[i] === this.node) {
        return this.setKey(i);
      }
    }
  } else {
    for (var key in this.container) {
      if (this.container[key] === this.node) {
        return this.setKey(key);
      }
    }
  }

  this.key = null;
}

export function _resyncList() {
  var listKey    = this.listKey;
  var parentPath = this.parentPath;
  if (!listKey || !parentPath || !parentPath.node) return;

  var newContainer = parentPath.node[listKey];
  if (this.container === newContainer) return;

  // container is out of sync. this is likely the result of it being reassigned

  if (newContainer) {
    this.container = newContainer;
  } else {
    this.container = null;
  }
}

export function _resyncRemoved() {
  if (this.key == null || !this.container || this.container[this.key] !== this.node) {
    this._markRemoved();
  }
}

export function shiftContext() {
  this.contexts.shift();
  this.setContext(this.contexts[0]);
}

export function unshiftContext(context) {
  this.contexts.unshift(context);
  this.setContext(context);
}

export function setup(parentPath, container, listKey, key) {
  this.inList    = !!listKey;
  this.listKey   = listKey;
  this.parentKey = listKey || key;
  this.container = container;

  this.parentPath = parentPath || this.parentPath;
  this.setKey(key);
}

export function setKey(key) {
  this.key  = key;
  this.node = this.container[this.key];
  this.type = this.node && this.node.type;
}

export function queueNode(path) {
  for (var context of (this.contexts: Array)) {
    if (context.queue) {
      context.queue.push(path);
    }
  }
}
<commit_msg>don't allow visitor methods to return a new node to replace<commit_after>// This file contains methods responsible for maintaining a TraversalContext.

import traverse from "../index";

export function call(key) {
  var node = this.node;
  if (!node) return;

  var opts = this.opts;

  for (var fns of [opts[key], opts[node.type] && opts[node.type][key]]){
    if (!fns) continue;

    for (var fn of (fns: Array)) {
      if (!fn) continue;

      let node = this.node;
      if (!node) return;

      var previousType = this.type;

      // call the function with the params (node, parent, scope, state)
      var replacement = fn.call(this, this, this.state);

      if (replacement) {
        throw new Error("Unexpected return value from visitor method");
      }

      if (this.shouldStop || this.shouldSkip || this.removed) return;

      if (previousType !== this.type) {
        this.queueNode(this);
        return;
      }
    }
  }
}

export function isBlacklisted(): boolean {
  var blacklist = this.opts.blacklist;
  return blacklist && blacklist.indexOf(this.node.type) > -1;
}

export function visit(): boolean {
  if (this.isBlacklisted()) return false;
  if (this.opts.shouldSkip && this.opts.shouldSkip(this)) return false;

  this.call("enter");

  if (this.shouldSkip) {
    return this.shouldStop;
  }

  var node = this.node;
  var opts = this.opts;

  if (node) {
    if (Array.isArray(node)) {
      // traverse over these replacement nodes we purposely don't call exitNode
      // as the original node has been destroyed
      for (var i = 0; i < node.length; i++) {
        traverse.node(node[i], opts, this.scope, this.state, this, this.skipKeys);
      }
    } else {
      traverse.node(node, opts, this.scope, this.state, this, this.skipKeys);
      this.call("exit");
    }
  }

  return this.shouldStop;
}

export function skip() {
  this.shouldSkip = true;
}

export function skipKey(key) {
  this.skipKeys[key] = true;
}

export function stop() {
  this.shouldStop = true;
  this.shouldSkip = true;
}

export function setScope() {
  if (this.opts && this.opts.noScope) return;

  var target = this.context || this.parentPath;
  this.scope = this.getScope(target && target.scope);
  if (this.scope) this.scope.init();
}

export function setContext(context) {
  this.shouldSkip = false;
  this.shouldStop = false;
  this.removed    = false;
  this.skipKeys   = {};

  if (context) {
    this.context = context;
    this.state   = context.state;
    this.opts    = context.opts;
  }

  this.setScope();

  return this;
}

/**
 * Here we resync the node paths `key` and `container`. If they've changed according
 * to what we have stored internally then we attempt to resync by crawling and looking
 * for the new values.
 */

export function resync() {
  if (this.removed) return;

  this._resyncParent();
  this._resyncList();
  this._resyncKey();
  //this._resyncRemoved();
}

export function _resyncParent() {
  if (this.parentPath) {
    this.parent = this.parentPath.node;
  }
}

export function _resyncKey() {
  if (!this.container) return;

  if (this.node === this.container[this.key]) return;

  // grrr, path key is out of sync. this is likely due to a modification to the AST
  // not done through our path APIs

  if (Array.isArray(this.container)) {
    for (var i = 0; i < this.container.length; i++) {
      if (this.container[i] === this.node) {
        return this.setKey(i);
      }
    }
  } else {
    for (var key in this.container) {
      if (this.container[key] === this.node) {
        return this.setKey(key);
      }
    }
  }

  this.key = null;
}

export function _resyncList() {
  var listKey    = this.listKey;
  var parentPath = this.parentPath;
  if (!listKey || !parentPath || !parentPath.node) return;

  var newContainer = parentPath.node[listKey];
  if (this.container === newContainer) return;

  // container is out of sync. this is likely the result of it being reassigned

  if (newContainer) {
    this.container = newContainer;
  } else {
    this.container = null;
  }
}

export function _resyncRemoved() {
  if (this.key == null || !this.container || this.container[this.key] !== this.node) {
    this._markRemoved();
  }
}

export function shiftContext() {
  this.contexts.shift();
  this.setContext(this.contexts[0]);
}

export function unshiftContext(context) {
  this.contexts.unshift(context);
  this.setContext(context);
}

export function setup(parentPath, container, listKey, key) {
  this.inList    = !!listKey;
  this.listKey   = listKey;
  this.parentKey = listKey || key;
  this.container = container;

  this.parentPath = parentPath || this.parentPath;
  this.setKey(key);
}

export function setKey(key) {
  this.key  = key;
  this.node = this.container[this.key];
  this.type = this.node && this.node.type;
}

export function queueNode(path) {
  for (var context of (this.contexts: Array)) {
    if (context.queue) {
      context.queue.push(path);
    }
  }
}
<|endoftext|>