<commit_before>(function($){

    var data = {
        state : {
            isOverWorld: function(){
                return $('.overworld-locations').is(':visible');
            },
            isChooseParty: function(){
                return $('.page-dungeon-chooseparty').is(':visible');
            },
            isAdventure: function(){
                return $('.dungeon-map-inner').is(':visible') && !$('.modal-window').is(':visible');
            },
            isSelectEncounterCompanion: function(){
                return $('.encounter-party-list').is(':visible');
            },
            isEncounter: function(){
                return $('.page-dungeon-combat').is(':visible') &&
                        (!$('.modal-window').is(':visible') ||
                          $('.modal-confirm.combat-wild > h3:contains(' + data.text.criticalHit + ')').is(':visible'));
            },
            isDiceRoller: function(){
                return $('.combatDiceBox').is(':visible') && !$('.modal-window').is(':visible');
            },
            isCombatVictory: function(){
                return $('.modal-confirm.combat-victory').is(':visible');
            },
            isModal: function(){
                return $('.modal-window').is(':visible');
            }
        },
        text:{
            chooseYourParty: 'Choose Your Party',
            ok: 'OK',
            d20: 'D20',
            criticalHit: 'Critical Hit!'
        }
    };

    var Adventure = function(character){

        var _adv = [
            {
                tier: "tier-[1|2|3|4|5|6]",
                companions: [
                    {
                        "name":"cleric",
                        "required":true||false,
                    },
                    {
                        "name":"guy",
                        "required":true||false,
                    }
                ]
            },
            {
                tier: "tier-1",
                companions: [
                    {
                        "name":"lowbies",
                        "required":true||false,
                    }
                ]
            },

        ];

        this.character = character;
        this.changeCharacter = $.nwg.changeCharacter.create(this.character);
        this.adventures = this.character.adv;
        this.dicePicker = $.nwg.dicePicker.create(this.character, $.nwg.dicePickerBrain.create());
    };

    Adventure.prototype.make_adventure_active = function(task) {
        $('.nav-dungeons').trigger('click');
        return {
            error: false,
            delay: 3000
        };
    };

    Adventure.prototype.check_adventure_state = function(task) {
        //console.log("check_adventure_state");
        //var task = self.crete_base_task();
        /*
            Should only be called from the crate_base_task method
        */

        if(data.state.isOverWorld()){
            //console.log("isOverWorld");
            task.then(this.start_adventure.bind(this))
            task.then(this.confirm_adventure.bind(this));
        }
        else if(data.state.isChooseParty()){
            //console.log("isChooseParty");
            task.then(this.clear_adventure_party.bind(this));
            task.then(this.select_adventure_party.bind(this));
            task.then(this.comfirm_adventure_party.bind(this));
        }
        else if(data.state.isAdventure()){
            //console.log("isAdventure");
            task.then(this.select_encounter.bind(this));
            task.then(this.select_encounter_companion.bind(this));
        }
        else if(data.state.isEncounter()){
            //console.log("isEncounter");
            task.then(this.dicePicker.pick_die.bind(this.dicePicker));
            task.then(this.clear_modal.bind(this));
        }
        else if(data.state.isSelectEncounterCompanion()){
            //console.log("isSelectEncounterCompanion");
            task.then(this.select_encounter_companion.bind(this));
        }
        else if(data.state.isCombatVictory()){
            //console.log("isCombatVictory");
            task.then(this.clear_modal.bind(this));
        }
        else if (data.state.isModal()){
            //console.log("isModal");
            task.then(this.clear_modal.bind(this));
        }
        //else

        return {error:false, delay: 3000};
    };

    Adventure.prototype.start_adventure = function(task) {
        //console.log("start_adventure");
        var curTier = this.adventures[0].tier;
        //console.log("Running " + curTier);
        $('a.' + curTier).trigger('click');

        return {
            error: false,
            delay: 3000
        };
    };

    Adventure.prototype.confirm_adventure = function(task) {
        //console.log("confirm_adventure");
        $('.choosePartyButton > button:contains(' + data.text.chooseYourParty + ')').trigger('click');

        var new_task = new $.nwg.adventure.create(this.character).create_base_task();
        new_task.start_in(1000);

        task.finish();
    };

    Adventure.prototype.select_adventure_party = function(task) {
        //console.log("select_adventure_party");
        //select adventure party member (which attemps to cancel the confirm if up THEN clears THEN selects)
        var PARTY_SIZE = 4;
        var adventureCompanions = this.adventures[0].companions;
        var self = this;
        var companionsToSelect = [];

        var requiredCompanions = [];
        var optionalCompanions = [];
        var totalCompanionCount = $('.party-entry.full-sheet:not(.promo)').length;
        var disabledCount = $('.party-entry.full-sheet.disabled:not(.training)').length;
        var trainingCount = $('.party-entry.full-sheet.training').length;
        var maxAvailableCount = totalCompanionCount - disabledCount;
        var availableCompanions = $('.party-entry.full-sheet.available:not(.promo)>a:not(.selected)');

        //console.log("available party");
        //console.log(availableCompanions);
        //console.log("total: " + totalCompanionCount);
        //console.log("disabled: " + disabledCount);
        //console.log("training: " + trainingCount);
        //console.log("maxAvailable: " + maxAvailableCount);
        if(adventureCompanions && adventureCompanions.length > 0 &&
            totalCompanionCount > PARTY_SIZE && maxAvailableCount > PARTY_SIZE){
            //console.log("many available");

            $(availableCompanions).each(function(indx, aCmp){
                var aComp = $(aCmp);
                var matched = false;
                $(adventureCompanions).each(function(indx, cmp){
                    var companion = aComp.has(':contains(' + cmp.name + ')');
                    if(companion.length === 1){
                        matched = true;
                        if(cmp.required){
                            requiredCompanions.push(companion);
                        }
                        else if(!cmp.excluded){
                            optionalCompanions.push(companion);
                        }
                    }
                });
                if(!matched){
                    optionalCompanions.push(aComp);
                }
                //console.log("rC=" + requiredCompanions.length + " | oC=" + optionalCompanions.length);
            });

            if(requiredCompanions.length + optionalCompanions.length >= PARTY_SIZE){

                for(var i = 0; i < requiredCompanions.length && i < PARTY_SIZE; i++){
                    companionsToSelect.push($(requiredCompanions[i]));
                }

                for(var ii = 0; ii < optionalCompanions.length && ii < PARTY_SIZE && ii < PARTY_SIZE - requiredCompanions.length; ii++){
                    companionsToSelect.push($(optionalCompanions[ii]));
                }

            }
        }
        else if((totalCompanionCount <= PARTY_SIZE && disabledCount === 0) ||
                (totalCompanionCount >= PARTY_SIZE)){
            //console.log("available compansions are all");
            companionsToSelect = availableCompanions;
        }

        if(companionsToSelect.length > 0){
            for(var i = 0; i < companionsToSelect.length && i < PARTY_SIZE; i++){
                $(companionsToSelect[i]).trigger('click');
            }
        }
        else{
            //console.log("Not enough compansions available")
            var delay = this.get_delay();

            var character = this.character;
            //character.adv.push(character.adv.shift());

            var newAdv = new $.nwg.adventure.create(character);
            var new_task = newAdv.create_base_task();
            new_task.then(newAdv.back_to_map.bind());
            new_task.start_in(delay);

            //Go to the next adventure group

            task.finish();
            return;
        }

        return {error:false, delay: 2000};
    };

    Adventure.prototype.back_to_map = function(task) {
        $('a.chooseparty-back').trigger('click');
        return {error:false, delay: 2000};
    };

    Adventure.prototype.clear_adventure_party = function(task){
        //console.log("clear_adventure_party");
        var partyCloseButtons = $('.party-entry > button.close-button');
        partyCloseButtons.each(function(idx, btn) {
            $(btn).trigger('click');
        });

        return {
            error: false,
            delay: 1000
        };
    };

    Adventure.prototype.comfirm_adventure_party = function(task) {
        //console.log("comfirm_adventure_party");
        $('.modal-window  button:contains(' + data.text.ok + ')').trigger('click');

        var new_task = new $.nwg.adventure.create(this.character).create_base_task();
        new_task.start_in(1000);

        task.finish();
    };

    Adventure.prototype.select_encounter = function(task) {
        //console.log("select_encounter");
        if($('.dungeon-map-inner').length === 0){
            //console.log("Dungeon not ready");
            return {
                error: false,
                delay: 1000
            };
        }
        var encounters = $('.overlay.button:not(.complete, .exit, .boss)');
        var boss = $('.overlay.button.boss');

        //console.log("[encounters=" + encounters.length + "]");
        if(encounters.length > 0){
            encounters.eq(0).trigger('click');
        }
        else if(boss.length > 0){
            boss.eq(0).trigger('click');
        }

        return {
            error: false,
            delay: 1000
        };
    };

    Adventure.prototype.select_encounter_companion = function(task) {
        //console.log("select_encounter_companion");
        var companions = $('a.selectable');
        if(companions.length >= 1){
            $('a.selectable').eq(0).trigger('click');
        };

        var new_task = new $.nwg.adventure.create(this.character).create_base_task();
        new_task.start_in(1000);

        task.finish();
    };

    Adventure.prototype.clear_modal = function(task) {
        var m = $('.modal-window');

        var okBtn = m.find('button:contains(' + data.text.ok + ')')
        var d20Btn = m.find('button:contains('+ data.text.d20+')');

        if(okBtn.length === 1){
            okBtn.trigger('click');
        }
        if(d20Btn.l === 1){
            d20Btn.trigger('click');
        }

        var new_task = new $.nwg.adventure.create(this.character).create_base_task();
        new_task.start_in(1000);

        task.finish();
    };

    Adventure.prototype.get_delay = function(task) {

        var availableCompanions = $('.party-entry.full-sheet.available:not(.promo)>a:not(.selected)');
        if(availableCompanions.length >= 4){
            //The idea here is that SOME set might have the ability to run, so we need to try
            return 60 * 1000;//in a minute
        }
        var reqStam = $('.chooseparty-stamina .number').eq(0).text();
        var belowStamComp = $('.party-entry.full-sheet.disabled .party-stamina')

        belowStamComp.sort(function(l, r){
            return (parseInt($(r).find('.below').text()) || 0) < (parseInt($(l).find('.below').text()) || 0);
        });//Sorts lowest first

        var stamDown = belowStamComp.eq(0).text();
        var missing = reqStam - stamDown;
        if(missing <= 0){
            return 0;
        }
        var regenDelay = ((((missing-1)*8)+1) * 60 * 1000);//Check in minutes if there's enough stamina

        var d = new Date();
        d.setMilliseconds(d.getMilliseconds() + regenDelay);
        console.log("[Sword Coast Adventure for " + this.character.name + " delayed for "
            + regenDelay + " ms at " + new Date().toLocaleString()
            + " resuming at " + d.toLocaleString());
    };

    Adventure.prototype.create_base_task = function create_base_task() {
        //console.log("create_base_task");
        var self = this;
        var task = $.task.create(this.changeCharacter.activate.bind(this.changeCharacter));
        task.then(this.make_adventure_active.bind(this));
        task.then(this.check_adventure_state.bind(this));

        return task;
    };

    $.extend(true, $.nwg, {
        adventure: {
            create:function(character){
                return new Adventure(character);
            }
        }
    });

}(jQuery));<commit_msg>binded<commit_after>(function($){

    var data = {
        state : {
            isOverWorld: function(){
                return $('.overworld-locations').is(':visible');
            },
            isChooseParty: function(){
                return $('.page-dungeon-chooseparty').is(':visible');
            },
            isAdventure: function(){
                return $('.dungeon-map-inner').is(':visible') && !$('.modal-window').is(':visible');
            },
            isSelectEncounterCompanion: function(){
                return $('.encounter-party-list').is(':visible');
            },
            isEncounter: function(){
                return $('.page-dungeon-combat').is(':visible') &&
                        (!$('.modal-window').is(':visible') ||
                          $('.modal-confirm.combat-wild > h3:contains(' + data.text.criticalHit + ')').is(':visible'));
            },
            isDiceRoller: function(){
                return $('.combatDiceBox').is(':visible') && !$('.modal-window').is(':visible');
            },
            isCombatVictory: function(){
                return $('.modal-confirm.combat-victory').is(':visible');
            },
            isModal: function(){
                return $('.modal-window').is(':visible');
            }
        },
        text:{
            chooseYourParty: 'Choose Your Party',
            ok: 'OK',
            d20: 'D20',
            criticalHit: 'Critical Hit!'
        }
    };

    var Adventure = function(character){

        var _adv = [
            {
                tier: "tier-[1|2|3|4|5|6]",
                companions: [
                    {
                        "name":"cleric",
                        "required":true||false,
                    },
                    {
                        "name":"guy",
                        "required":true||false,
                    }
                ]
            },
            {
                tier: "tier-1",
                companions: [
                    {
                        "name":"lowbies",
                        "required":true||false,
                    }
                ]
            },

        ];

        this.character = character;
        this.changeCharacter = $.nwg.changeCharacter.create(this.character);
        this.adventures = this.character.adv;
        this.dicePicker = $.nwg.dicePicker.create(this.character, $.nwg.dicePickerBrain.create());
    };

    Adventure.prototype.make_adventure_active = function(task) {
        $('.nav-dungeons').trigger('click');
        return {
            error: false,
            delay: 3000
        };
    };

    Adventure.prototype.check_adventure_state = function(task) {
        //console.log("check_adventure_state");
        //var task = self.crete_base_task();
        /*
            Should only be called from the crate_base_task method
        */

        if(data.state.isOverWorld()){
            //console.log("isOverWorld");
            task.then(this.start_adventure.bind(this))
            task.then(this.confirm_adventure.bind(this));
        }
        else if(data.state.isChooseParty()){
            //console.log("isChooseParty");
            task.then(this.clear_adventure_party.bind(this));
            task.then(this.select_adventure_party.bind(this));
            task.then(this.comfirm_adventure_party.bind(this));
        }
        else if(data.state.isAdventure()){
            //console.log("isAdventure");
            task.then(this.select_encounter.bind(this));
            task.then(this.select_encounter_companion.bind(this));
        }
        else if(data.state.isEncounter()){
            //console.log("isEncounter");
            task.then(this.dicePicker.pick_die.bind(this.dicePicker));
            task.then(this.clear_modal.bind(this));
        }
        else if(data.state.isSelectEncounterCompanion()){
            //console.log("isSelectEncounterCompanion");
            task.then(this.select_encounter_companion.bind(this));
        }
        else if(data.state.isCombatVictory()){
            //console.log("isCombatVictory");
            task.then(this.clear_modal.bind(this));
        }
        else if (data.state.isModal()){
            //console.log("isModal");
            task.then(this.clear_modal.bind(this));
        }
        //else

        return {error:false, delay: 3000};
    };

    Adventure.prototype.start_adventure = function(task) {
        //console.log("start_adventure");
        var curTier = this.adventures[0].tier;
        //console.log("Running " + curTier);
        $('a.' + curTier).trigger('click');

        return {
            error: false,
            delay: 3000
        };
    };

    Adventure.prototype.confirm_adventure = function(task) {
        //console.log("confirm_adventure");
        $('.choosePartyButton > button:contains(' + data.text.chooseYourParty + ')').trigger('click');

        var new_task = new $.nwg.adventure.create(this.character).create_base_task();
        new_task.start_in(1000);

        task.finish();
    };

    Adventure.prototype.select_adventure_party = function(task) {
        //console.log("select_adventure_party");
        //select adventure party member (which attemps to cancel the confirm if up THEN clears THEN selects)
        var PARTY_SIZE = 4;
        var adventureCompanions = this.adventures[0].companions;
        var self = this;
        var companionsToSelect = [];

        var requiredCompanions = [];
        var optionalCompanions = [];
        var totalCompanionCount = $('.party-entry.full-sheet:not(.promo)').length;
        var disabledCount = $('.party-entry.full-sheet.disabled:not(.training)').length;
        var trainingCount = $('.party-entry.full-sheet.training').length;
        var maxAvailableCount = totalCompanionCount - disabledCount;
        var availableCompanions = $('.party-entry.full-sheet.available:not(.promo)>a:not(.selected)');

        //console.log("available party");
        //console.log(availableCompanions);
        //console.log("total: " + totalCompanionCount);
        //console.log("disabled: " + disabledCount);
        //console.log("training: " + trainingCount);
        //console.log("maxAvailable: " + maxAvailableCount);
        if(adventureCompanions && adventureCompanions.length > 0 &&
            totalCompanionCount > PARTY_SIZE && maxAvailableCount > PARTY_SIZE){
            //console.log("many available");

            $(availableCompanions).each(function(indx, aCmp){
                var aComp = $(aCmp);
                var matched = false;
                $(adventureCompanions).each(function(indx, cmp){
                    var companion = aComp.has(':contains(' + cmp.name + ')');
                    if(companion.length === 1){
                        matched = true;
                        if(cmp.required){
                            requiredCompanions.push(companion);
                        }
                        else if(!cmp.excluded){
                            optionalCompanions.push(companion);
                        }
                    }
                });
                if(!matched){
                    optionalCompanions.push(aComp);
                }
                //console.log("rC=" + requiredCompanions.length + " | oC=" + optionalCompanions.length);
            });

            if(requiredCompanions.length + optionalCompanions.length >= PARTY_SIZE){

                for(var i = 0; i < requiredCompanions.length && i < PARTY_SIZE; i++){
                    companionsToSelect.push($(requiredCompanions[i]));
                }

                for(var ii = 0; ii < optionalCompanions.length && ii < PARTY_SIZE && ii < PARTY_SIZE - requiredCompanions.length; ii++){
                    companionsToSelect.push($(optionalCompanions[ii]));
                }

            }
        }
        else if((totalCompanionCount <= PARTY_SIZE && disabledCount === 0) ||
                (totalCompanionCount >= PARTY_SIZE)){
            //console.log("available compansions are all");
            companionsToSelect = availableCompanions;
        }

        if(companionsToSelect.length > 0){
            for(var i = 0; i < companionsToSelect.length && i < PARTY_SIZE; i++){
                $(companionsToSelect[i]).trigger('click');
            }
        }
        else{
            //console.log("Not enough compansions available")
            var delay = this.get_delay();

            var character = this.character;
            //character.adv.push(character.adv.shift());

            var newAdv = new $.nwg.adventure.create(character);
            var new_task = newAdv.create_base_task();
            new_task.then(newAdv.back_to_map.bind(newAdv));
            new_task.start_in(delay);

            //Go to the next adventure group

            task.finish();
            return;
        }

        return {error:false, delay: 2000};
    };

    Adventure.prototype.back_to_map = function(task) {
        $('a.chooseparty-back').trigger('click');
        return {error:false, delay: 2000};
    };

    Adventure.prototype.clear_adventure_party = function(task){
        //console.log("clear_adventure_party");
        var partyCloseButtons = $('.party-entry > button.close-button');
        partyCloseButtons.each(function(idx, btn) {
            $(btn).trigger('click');
        });

        return {
            error: false,
            delay: 1000
        };
    };

    Adventure.prototype.comfirm_adventure_party = function(task) {
        //console.log("comfirm_adventure_party");
        $('.modal-window  button:contains(' + data.text.ok + ')').trigger('click');

        var new_task = new $.nwg.adventure.create(this.character).create_base_task();
        new_task.start_in(1000);

        task.finish();
    };

    Adventure.prototype.select_encounter = function(task) {
        //console.log("select_encounter");
        if($('.dungeon-map-inner').length === 0){
            //console.log("Dungeon not ready");
            return {
                error: false,
                delay: 1000
            };
        }
        var encounters = $('.overlay.button:not(.complete, .exit, .boss)');
        var boss = $('.overlay.button.boss');

        //console.log("[encounters=" + encounters.length + "]");
        if(encounters.length > 0){
            encounters.eq(0).trigger('click');
        }
        else if(boss.length > 0){
            boss.eq(0).trigger('click');
        }

        return {
            error: false,
            delay: 1000
        };
    };

    Adventure.prototype.select_encounter_companion = function(task) {
        //console.log("select_encounter_companion");
        var companions = $('a.selectable');
        if(companions.length >= 1){
            $('a.selectable').eq(0).trigger('click');
        };

        var new_task = new $.nwg.adventure.create(this.character).create_base_task();
        new_task.start_in(1000);

        task.finish();
    };

    Adventure.prototype.clear_modal = function(task) {
        var m = $('.modal-window');

        var okBtn = m.find('button:contains(' + data.text.ok + ')')
        var d20Btn = m.find('button:contains('+ data.text.d20+')');

        if(okBtn.length === 1){
            okBtn.trigger('click');
        }
        if(d20Btn.l === 1){
            d20Btn.trigger('click');
        }

        var new_task = new $.nwg.adventure.create(this.character).create_base_task();
        new_task.start_in(1000);

        task.finish();
    };

    Adventure.prototype.get_delay = function(task) {

        var availableCompanions = $('.party-entry.full-sheet.available:not(.promo)>a:not(.selected)');
        if(availableCompanions.length >= 4){
            //The idea here is that SOME set might have the ability to run, so we need to try
            return 60 * 1000;//in a minute
        }
        var reqStam = $('.chooseparty-stamina .number').eq(0).text();
        var belowStamComp = $('.party-entry.full-sheet.disabled .party-stamina')

        belowStamComp.sort(function(l, r){
            return (parseInt($(r).find('.below').text()) || 0) < (parseInt($(l).find('.below').text()) || 0);
        });//Sorts lowest first

        var stamDown = belowStamComp.eq(0).text();
        var missing = reqStam - stamDown;
        if(missing <= 0){
            return 0;
        }
        var regenDelay = ((((missing-1)*8)+1) * 60 * 1000);//Check in minutes if there's enough stamina

        var d = new Date();
        d.setMilliseconds(d.getMilliseconds() + regenDelay);
        console.log("[Sword Coast Adventure for " + this.character.name + " delayed for "
            + regenDelay + " ms at " + new Date().toLocaleString()
            + " resuming at " + d.toLocaleString());
    };

    Adventure.prototype.create_base_task = function create_base_task() {
        //console.log("create_base_task");
        var self = this;
        var task = $.task.create(this.changeCharacter.activate.bind(this.changeCharacter));
        task.then(this.make_adventure_active.bind(this));
        task.then(this.check_adventure_state.bind(this));

        return task;
    };

    $.extend(true, $.nwg, {
        adventure: {
            create:function(character){
                return new Adventure(character);
            }
        }
    });

}(jQuery));<|endoftext|>


<commit_before>'use strict';

const db = require('../db');
const debug = require('debug');
const info = debug('arangodb#resources:info');
const trace = debug('arangodb#resources:trace');
const _ = require('lodash');
var Promise = require('bluebird');
const aql = require('arangojs').aqlQuery;
const pointer = require('json-pointer');
const config = require('../config');
const util = require('../util');
config.set('isTest', true);
const resources =
    db.collection(config.get('arangodb:collections:resources:name'));
const graphNodes =
    db.collection(config.get('arangodb:collections:graphNodes:name'));
const edges =
    db.collection(config.get('arangodb:collections:edges:name'));

function lookupFromUrl(url, user_id) {
  return Promise.try(() => {
    trace(user_id)
    let pieces = pointer.parse(url);
    // resources/123 => graphNodes/resources:123
    var startNode = graphNodes.name + '/' + pieces[0] + ':' + pieces[1];
    let id = pieces.splice(0, 2);
    // Create a filter for each segment of the url
    const filters = pieces.map((urlPiece, i) => {
      return `FILTER p.edges[${i}].name == '${urlPiece}' || p.edges[${i}].name == null`
    }).join(' ');
    trace('FILTERSSSS', filters)
    let query = `
      LET path = LAST(
        FOR v, e, p IN 0..${pieces.length}
          OUTBOUND '${startNode}'
          ${edges.name}
          ${filters}
          RETURN p
      )
      LET resources = DOCUMENT(path.vertices[*].resource_id)
      RETURN MERGE(path, {permissions:
      resources[*]._meta._permissions['${user_id}']})
    `;
    trace(`lookupFromUrl(${url})`, `running query: ${query}`);
    return db.query({query}).call('next').then((result) => {

      trace('query result = ', JSON.stringify(result, false, '  '));
      let resourceId = '';
      let pathLeftover = pointer.compile(id.concat(pieces));

      if (result.length < 1) {
        trace('lookupFromUrl(' + url + '): result path length < 1');
        return {'resource_id': resourceId, 'path_leftover': pathLeftover,
          permissions: {}};
      }

      let permissions = {}
      result.permissions.reverse().some((p, i) => {
        if (p) {
          if (permissions.read === undefined) permissions.read = p.read
          if (permissions.write === undefined) permissions.write = p.write
          if (permissions.owner === undefined) permissions.owner = p.owner
          if (permissions.read !== undefined && permissions.write !== undefined
            && permissions.owner !== undefined) return true
        }
      })

      // Check for a traversal that did not finish (aka not found)
      if (result.vertices[0] === null) {
        trace('lookupFromUrl(' + url + '):', 'result.vertices[0] === null');
        return {'resource_id': resourceId, 'path_leftover': pathLeftover,
          permissions};
      }

      trace('longest path has ' + result.vertices.length + ' vertices');
      if (!result.vertices[result.vertices.length - 1]) {
        resourceId = '';
        pathLeftover = result.edges[result.edges.length - 1]._to
            .replace(/^graphNodes\//, '/')
            .replace(/resources:/, 'resources/');
        return {'resource_id': resourceId, 'path_leftover': pathLeftover,
          permissions};
      }
      resourceId = result.vertices[result.vertices.length - 1]['resource_id'];
      // If the desired url has more pieces than the longest path, the
      // pathLeftover is the extra pieces
      if (result.vertices.length - 1 < pieces.length) {
        trace('lookupFromUrl(' + url + '):',
            'more URL pieces than vertices, computing path');
        let lastResource = (result.vertices.length - 1) -
            (_.findIndex(_.reverse(result.vertices), 'is_resource'));
        // Slice a negative value to take the last n pieces of the array
        pathLeftover =
            pointer.compile(pieces.slice(lastResource - pieces.length));
      } else {
        trace('lookupFromUrl(' + url + '):',
            'same number URL pieces as vertices, path is on graphNode');
        pathLeftover = result.vertices[result.vertices.length - 1].path || '';
      }

      return {'resource_id': resourceId, 'path_leftover': pathLeftover,
        permissions};
    });
  });
}

function getResource(id, path) {
  // TODO: Escaping stuff?
  const parts = (path || '')
    .split('/')
    .filter(x => !!x);

  let bindVars = parts.reduce((b, part, i) => {
    b[`v${i}`] = part;
    return b;
  }, {});
  bindVars.id = id;
  bindVars['@collection'] = resources.name;

  const returnPath = parts.reduce((p, part, i) => p.concat(`[@v${i}]`), '');

  return db.query({
    query: `FOR r IN @@collection
        FILTER r._id == @id
        RETURN r${returnPath}`,
    bindVars
  }).then(result => result.next())
  .then(util.sanitizeResult)
  .catch({
      isArangoError: true,
      errorMessage: 'invalid traversal depth (while instantiating plan)'
    },
    () => null); // Treat non-existing path has not-found
}

function getResourceOwnerIdRev(id) {
  return db.query({
    query: `
      FOR r IN @@collection
        FILTER r._id == @id
        RETURN {
          _rev: r._oada_rev,
          _id: r._id,
          _meta: { _owner: r._meta._owner }
        }
    `,
    bindVars: {
      id,
      '@collection': resources.name
    } // bind id to @id
  }).then(result => result.next())
  .then(obj => {
    trace('getResourceOwnerIdRev(' + id + '): result = ', obj);
    return obj;
  }).catch({
      isArangoError: true,
      errorMessage: 'invalid traversal depth (while instantiating plan)'
    },
    () => null
  ); // Treat non-existing path has not-found
}

function getParents(id) {
  let collection = graphNodes.name;
  if (!id.match(/^\//)) {
    // if no leading slash on resourceid, add it to graphNodes
    collection += '/';
  }

  let bindVars = {
    // have to take out the slash on resources/ for arango to allow as key
    'id': collection + id.replace(/resources\//, 'resources:'),
    '@edges': edges.name
  };

  let parents = [];
  let parent = {
    'resource_id': null,
    'path': null,
    'contentType': null
  };

  let parentQuery = `FOR v, e IN 0..1
      INBOUND @id
      @@edges
      FILTER e.versioned == true
      RETURN {v:v, e:e}`;

  return db.query({
    query: parentQuery,
    bindVars
  })
  .then((cursor) => {
    let i = 0;

    // console.log(cursor._result);
    let length = cursor._result.length;
    trace('getParents' + '(' + id + ')' + ' parents length is ' + length);

    for (i = 0; i < length; i++) {
      parent['resource_id'] = cursor._result[i].v['resource_id'];
      let path = cursor._result[i].v.path;
      if (!path) {
        path = '';
      }
      parent.path = path + '/' + cursor._result[i].e.name;
      parents.splice(i, 0, parent);
    }

    return Promise.map(parents, parent => {
      return db.query(aql`
        FOR r in resources
        FILTER r._id == ${parent['resource_id']}
        RETURN r._type`)
        .then(cursor => cursor.next())
        .then(result => {
          // console.log(parent);
          parent.contentType = result;
        });
    })
    .then(() => {
      // all done
      return parents;
    });
  })
  .catch({
      isArangoError: true,
      errorMessage: 'invalid traversal depth (while instantiating plan)'
    },
    () => null); // Treat non-existing path has not-found
}

function putResource(id, obj) {
  // Fix rev
  obj['_oada_rev'] = obj['_rev'];
  obj['_rev'] = undefined;

  // TODO: Sanitize OADA keys?

  obj['_key'] = id.replace(/^resources\//, '');
  var start = new Date().getTime();
  info(`Adding links for resource ${id}...`);
  return addLinks(obj).then(function docUpsert(links) {
    var end = new Date().getTime();
    info(`Links added for resource ${id}. Upserting. +${end - start}ms`);
    debug(`Upserting resource ${obj['_key']}`);
    debug(`Upserting links: ${JSON.stringify(links, null, 2)}`);

    // TODO: Should it check that graphNodes exist but are wrong?
    var q;
    if (links.length > 0) {
      q = db.query(aql`
        LET reskey = ${obj['_key']}
        LET res = FIRST(
          LET res = ${obj}
          UPSERT { '_key': reskey }
          INSERT res
          UPDATE res
          IN resources
          RETURN NEW
        )
        FOR l IN ${links}
          LET lkey = SUBSTRING(l._id, LENGTH('resources/'))
          LET nodeids = FIRST(
            LET nodeids = (
              LET nodeids = APPEND([reskey], SLICE(l.path, 0, -1))
              FOR i IN 1..LENGTH(nodeids)
                LET path = CONCAT_SEPARATOR(':', SLICE(nodeids, 0, i))
                RETURN CONCAT('resources:', path)
            )
            LET end = CONCAT('resources:', lkey)
            RETURN APPEND(nodeids, [end])
          )
          LET nodes = APPEND((
            FOR i IN 0..(LENGTH(l.path)-1)
              LET isresource = i IN [0, LENGTH(l.path)]
              LET ppath = SLICE(l.path, 0, i)
              LET resid = i == LENGTH(l.path) ? lkey : reskey
              LET path = isresource ? null
                  : CONCAT_SEPARATOR('/', APPEND([''], ppath))
              UPSERT { '_key': nodeids[i] }
              INSERT {
                '_key': nodeids[i],
                'is_resource': isresource,
                'path': path,
                'resource_id': res._id
              }
              UPDATE {}
              IN graphNodes
              RETURN NEW
          ), { _id: CONCAT('graphNodes/', LAST(nodeids)), is_resource: true })
          LET edges = (
            FOR i IN 0..(LENGTH(l.path)-1)
              UPSERT {
                '_key': MD5(nodes[i]._id+nodes[i+1]._id),
                '_from': nodes[i]._id,
                'name': l.path[i]
              }
              INSERT {
                '_from': nodes[i]._id,
                '_to': nodes[i+1]._id,
                'name': l.path[i],
                'versioned': nodes[i+1].is_resource && HAS(l, '_rev')
              }
              UPDATE {
                '_to': nodes[i+1]._id,
                'versioned': nodes[i+1].is_resource && HAS(l, '_rev')
              }
              IN edges
              RETURN NEW
          )
          FOR edge IN edges
            RETURN edge._key
      `);
    } else {
      q = db.query(aql`
        LET res = FIRST(
          LET res = ${obj}
          UPSERT { '_key': res._key }
          INSERT res
          UPDATE res
          IN resources
          return NEW
        )
        LET nodekey = CONCAT('resources:', res._key)
        UPSERT { '_key': nodekey }
        INSERT {
          '_key': nodekey,
          'is_resource': true,
          'resource_id': res._id
        }
        UPDATE {}
        IN graphNodes
      `);
    }

    return q;
  });
}

// TODO: Remove links as well
function addLinks(res) {
  function forLinks(res, cb, path) {
    path = path || [];

    return Promise.map(Object.keys(res), key => {
      // Just ignore _meta for now TODO: Allow links in _meta?
      if (key === '_meta') {
        return;
      }

      if (res[key] && res[key].hasOwnProperty('_id')) {
        return cb(res[key], path.concat(key));
      } else if (typeof res[key] === 'object' && res[key] !== null) {
        return forLinks(res[key], cb, path.concat(key));
      }
    });
  }

  // TODO: Use fewer queries or something?
  var links = [];
  return forLinks(res, function processLinks(link, path) {

    var rev;
    if (link.hasOwnProperty('_rev')) {
      rev = getResource(link['_id'], '_oada_rev')
        .then(function updateRev(rev) {
          link['_rev'] = rev || '0-0';
        });
    }

    return Promise.resolve(rev).then(function() {
      links.push(Object.assign({path}, link));
    });
  }).then(() => links);
}

function deleteResource(id) {
  let key = id.replace(/^resources\//, '');

  // Query deletes resouce, its nodes, and outgoing edges (but not incoming)
  return db.query(aql`
    LET res = FIRST(
      REMOVE { '_key': ${key} } IN resources
      RETURN OLD
    )
    LET nodes = (
      FOR node in graphNodes
        FILTER node['resource_id'] == res._id
        REMOVE node IN graphNodes
        RETURN OLD
    )
    FOR node IN nodes
      FOR edge IN edges
        FILTER edge['_from'] == node._id
        REMOVE edge IN edges
  `);
}

// "Delete" a part of a resource
// TODO: Not too sure I like how this function works or its name...
function deletePartialResource(id, path, doc) {
  let key = id.replace(/^resources\//, '');
  doc = doc || {};

  // Fix rev
  doc['_oada_rev'] = doc['_rev'];
  doc['_rev'] = undefined;

  pointer.set(doc, path, null);

  // Regex to match starts of paths
  let preg = `^${Array.isArray(path) ? pointer.compile(path) : path}(/|$)`;

  return db.query(aql`
    LET res = FIRST(
      UPDATE { '_key': ${key} }
      WITH ${doc}
      IN resources
      OPTIONS { keepNull: false }
      RETURN NEW
    )
    LET nodes = (
      FOR node IN graphNodes
        FILTER node['resource_id'] == res._id AND node.path =~ ${preg}
        REMOVE node IN graphNodes
        RETURN OLD
    )
    FOR node IN nodes
      FOR edge IN edges
        FILTER edge['_from'] == node._id
        REMOVE edge IN edges
  `);
}

module.exports = {
  lookupFromUrl,
  getResource,
  getResourceOwnerIdRev,
  putResource,
  deleteResource,
  deletePartialResource,
  getParents,
  // TODO: Better way to handler errors?
  // ErrorNum from: https://docs.arangodb.com/2.8/ErrorCodes/
  NotFoundError: {
    name: 'ArangoError',
    errorNum: 1202
  },
  UniqueConstraintError: {
    name: 'ArangoError',
    errorNum: 1210
  },
};
<commit_msg>Add `from` field to graph url resolution<commit_after>'use strict';

const db = require('../db');
const debug = require('debug');
const info = debug('arangodb#resources:info');
const trace = debug('arangodb#resources:trace');
const _ = require('lodash');
var Promise = require('bluebird');
const aql = require('arangojs').aqlQuery;
const pointer = require('json-pointer');
const config = require('../config');
const util = require('../util');
config.set('isTest', true);
const resources =
    db.collection(config.get('arangodb:collections:resources:name'));
const graphNodes =
    db.collection(config.get('arangodb:collections:graphNodes:name'));
const edges =
    db.collection(config.get('arangodb:collections:edges:name'));

function lookupFromUrl(url, user_id) {
  return Promise.try(() => {
    trace(user_id)
    let pieces = pointer.parse(url);
    // resources/123 => graphNodes/resources:123
    var startNode = graphNodes.name + '/' + pieces[0] + ':' + pieces[1];
    let id = pieces.splice(0, 2);
    // Create a filter for each segment of the url
    const filters = pieces.map((urlPiece, i) => {
      return `FILTER p.edges[${i}].name == '${urlPiece}' || p.edges[${i}].name == null`
    }).join(' ');
    trace('FILTERSSSS', filters)
    let query = `
      LET path = LAST(
        FOR v, e, p IN 0..${pieces.length}
          OUTBOUND '${startNode}'
          ${edges.name}
          ${filters}
          RETURN p
      )
      LET resources = DOCUMENT(path.vertices[*].resource_id)
      RETURN MERGE(path, {permissions:
      resources[*]._meta._permissions['${user_id}']})
    `;
    trace(`lookupFromUrl(${url})`, `running query: ${query}`);
    return db.query({query}).call('next').then((result) => {

      trace('query result = ', JSON.stringify(result, false, '  '));
      let resourceId = '';
      let pathLeftover = pointer.compile(id.concat(pieces));
      // Also return info about parent?
      let from = {'resource_id': '', 'path': ''};

      if (result.length < 1) {
        trace('lookupFromUrl(' + url + '): result path length < 1');
        return {'resource_id': resourceId, 'path_leftover': pathLeftover,
          from, permissions: {}};
      }

      let permissions = {}
      result.permissions.reverse().some(p => {
        if (p) {
          if (permissions.read === undefined) {
            permissions.read = p.read;
          }
          if (permissions.write === undefined) {
            permissions.write = p.write;
          }
          if (permissions.owner === undefined) {
            permissions.owner = p.owner;
          }
          if (permissions.read !== undefined &&
              permissions.write !== undefined &&
              permissions.owner !== undefined) {
            return true;
          }
        }
      })

      // Check for a traversal that did not finish (aka not found)
      if (result.vertices[0] === null) {
        trace('lookupFromUrl(' + url + '):', 'result.vertices[0] === null');
        return {'resource_id': resourceId, 'path_leftover': pathLeftover,
          from, permissions};
      }

      trace('longest path has ' + result.vertices.length + ' vertices');
      if (!result.vertices[result.vertices.length - 1]) {
        resourceId = '';
        pathLeftover = result.edges[result.edges.length - 1]._to
            .replace(/^graphNodes\//, '/')
            .replace(/resources:/, 'resources/');
        return {'resource_id': resourceId, 'path_leftover': pathLeftover,
          from, permissions};
      }
      resourceId = result.vertices[result.vertices.length - 1]['resource_id'];
      from = result.vertices[result.vertices.length - 2];
      let edge = result.edges[result.edges.length - 1];
      // If the desired url has more pieces than the longest path, the
      // pathLeftover is the extra pieces
      if (result.vertices.length - 1 < pieces.length) {
        trace('lookupFromUrl(' + url + '):',
            'more URL pieces than vertices, computing path');
        let lastResource = (result.vertices.length - 1) -
            (_.findIndex(_.reverse(result.vertices), 'is_resource'));
        // Slice a negative value to take the last n pieces of the array
        pathLeftover =
            pointer.compile(pieces.slice(lastResource - pieces.length));
      } else {
        trace('lookupFromUrl(' + url + '):',
            'same number URL pieces as vertices, path is on graphNode');
        pathLeftover = result.vertices[result.vertices.length - 1].path || '';
      }

      return {
        'resource_id': resourceId,
        'path_leftover': pathLeftover,
        permissions,
        'from': {
          'resource_id': from ? from['resource_id'] : '',
          'path': (from && from['path'] || '') + (edge ? '/' + edge.name : '')
        }
      };
    });
  });
}

function getResource(id, path) {
  // TODO: Escaping stuff?
  const parts = (path || '')
    .split('/')
    .filter(x => !!x);

  let bindVars = parts.reduce((b, part, i) => {
    b[`v${i}`] = part;
    return b;
  }, {});
  bindVars.id = id;
  bindVars['@collection'] = resources.name;

  const returnPath = parts.reduce((p, part, i) => p.concat(`[@v${i}]`), '');

  return db.query({
    query: `FOR r IN @@collection
        FILTER r._id == @id
        RETURN r${returnPath}`,
    bindVars
  }).then(result => result.next())
  .then(util.sanitizeResult)
  .catch({
      isArangoError: true,
      errorMessage: 'invalid traversal depth (while instantiating plan)'
    },
    () => null); // Treat non-existing path has not-found
}

function getResourceOwnerIdRev(id) {
  return db.query({
    query: `
      FOR r IN @@collection
        FILTER r._id == @id
        RETURN {
          _rev: r._oada_rev,
          _id: r._id,
          _meta: { _owner: r._meta._owner }
        }
    `,
    bindVars: {
      id,
      '@collection': resources.name
    } // bind id to @id
  }).then(result => result.next())
  .then(obj => {
    trace('getResourceOwnerIdRev(' + id + '): result = ', obj);
    return obj;
  }).catch({
      isArangoError: true,
      errorMessage: 'invalid traversal depth (while instantiating plan)'
    },
    () => null
  ); // Treat non-existing path has not-found
}

function getParents(id) {
  let collection = graphNodes.name;
  if (!id.match(/^\//)) {
    // if no leading slash on resourceid, add it to graphNodes
    collection += '/';
  }

  let bindVars = {
    // have to take out the slash on resources/ for arango to allow as key
    'id': collection + id.replace(/resources\//, 'resources:'),
    '@edges': edges.name
  };

  let parents = [];
  let parent = {
    'resource_id': null,
    'path': null,
    'contentType': null
  };

  let parentQuery = `FOR v, e IN 0..1
      INBOUND @id
      @@edges
      FILTER e.versioned == true
      RETURN {v:v, e:e}`;

  return db.query({
    query: parentQuery,
    bindVars
  })
  .then((cursor) => {
    let i = 0;

    // console.log(cursor._result);
    let length = cursor._result.length;
    trace('getParents' + '(' + id + ')' + ' parents length is ' + length);

    for (i = 0; i < length; i++) {
      parent['resource_id'] = cursor._result[i].v['resource_id'];
      let path = cursor._result[i].v.path;
      if (!path) {
        path = '';
      }
      parent.path = path + '/' + cursor._result[i].e.name;
      parents.splice(i, 0, parent);
    }

    return Promise.map(parents, parent => {
      return db.query(aql`
        FOR r in resources
        FILTER r._id == ${parent['resource_id']}
        RETURN r._type`)
        .then(cursor => cursor.next())
        .then(result => {
          // console.log(parent);
          parent.contentType = result;
        });
    })
    .then(() => {
      // all done
      return parents;
    });
  })
  .catch({
      isArangoError: true,
      errorMessage: 'invalid traversal depth (while instantiating plan)'
    },
    () => null); // Treat non-existing path has not-found
}

function putResource(id, obj) {
  // Fix rev
  obj['_oada_rev'] = obj['_rev'];
  obj['_rev'] = undefined;

  // TODO: Sanitize OADA keys?

  obj['_key'] = id.replace(/^resources\//, '');
  var start = new Date().getTime();
  info(`Adding links for resource ${id}...`);
  return addLinks(obj).then(function docUpsert(links) {
    var end = new Date().getTime();
    info(`Links added for resource ${id}. Upserting. +${end - start}ms`);
    debug(`Upserting resource ${obj['_key']}`);
    debug(`Upserting links: ${JSON.stringify(links, null, 2)}`);

    // TODO: Should it check that graphNodes exist but are wrong?
    var q;
    if (links.length > 0) {
      q = db.query(aql`
        LET reskey = ${obj['_key']}
        LET res = FIRST(
          LET res = ${obj}
          UPSERT { '_key': reskey }
          INSERT res
          UPDATE res
          IN resources
          RETURN NEW
        )
        FOR l IN ${links}
          LET lkey = SUBSTRING(l._id, LENGTH('resources/'))
          LET nodeids = FIRST(
            LET nodeids = (
              LET nodeids = APPEND([reskey], SLICE(l.path, 0, -1))
              FOR i IN 1..LENGTH(nodeids)
                LET path = CONCAT_SEPARATOR(':', SLICE(nodeids, 0, i))
                RETURN CONCAT('resources:', path)
            )
            LET end = CONCAT('resources:', lkey)
            RETURN APPEND(nodeids, [end])
          )
          LET nodes = APPEND((
            FOR i IN 0..(LENGTH(l.path)-1)
              LET isresource = i IN [0, LENGTH(l.path)]
              LET ppath = SLICE(l.path, 0, i)
              LET resid = i == LENGTH(l.path) ? lkey : reskey
              LET path = isresource ? null
                  : CONCAT_SEPARATOR('/', APPEND([''], ppath))
              UPSERT { '_key': nodeids[i] }
              INSERT {
                '_key': nodeids[i],
                'is_resource': isresource,
                'path': path,
                'resource_id': res._id
              }
              UPDATE {}
              IN graphNodes
              RETURN NEW
          ), { _id: CONCAT('graphNodes/', LAST(nodeids)), is_resource: true })
          LET edges = (
            FOR i IN 0..(LENGTH(l.path)-1)
              UPSERT {
                '_key': MD5(nodes[i]._id+nodes[i+1]._id),
                '_from': nodes[i]._id,
                'name': l.path[i]
              }
              INSERT {
                '_from': nodes[i]._id,
                '_to': nodes[i+1]._id,
                'name': l.path[i],
                'versioned': nodes[i+1].is_resource && HAS(l, '_rev')
              }
              UPDATE {
                '_to': nodes[i+1]._id,
                'versioned': nodes[i+1].is_resource && HAS(l, '_rev')
              }
              IN edges
              RETURN NEW
          )
          FOR edge IN edges
            RETURN edge._key
      `);
    } else {
      q = db.query(aql`
        LET res = FIRST(
          LET res = ${obj}
          UPSERT { '_key': res._key }
          INSERT res
          UPDATE res
          IN resources
          return NEW
        )
        LET nodekey = CONCAT('resources:', res._key)
        UPSERT { '_key': nodekey }
        INSERT {
          '_key': nodekey,
          'is_resource': true,
          'resource_id': res._id
        }
        UPDATE {}
        IN graphNodes
      `);
    }

    return q;
  });
}

// TODO: Remove links as well
function addLinks(res) {
  function forLinks(res, cb, path) {
    path = path || [];

    return Promise.map(Object.keys(res), key => {
      // Just ignore _meta for now TODO: Allow links in _meta?
      if (key === '_meta') {
        return;
      }

      if (res[key] && res[key].hasOwnProperty('_id')) {
        return cb(res[key], path.concat(key));
      } else if (typeof res[key] === 'object' && res[key] !== null) {
        return forLinks(res[key], cb, path.concat(key));
      }
    });
  }

  // TODO: Use fewer queries or something?
  var links = [];
  return forLinks(res, function processLinks(link, path) {

    var rev;
    if (link.hasOwnProperty('_rev')) {
      rev = getResource(link['_id'], '_oada_rev')
        .then(function updateRev(rev) {
          link['_rev'] = rev || '0-0';
        });
    }

    return Promise.resolve(rev).then(function() {
      links.push(Object.assign({path}, link));
    });
  }).then(() => links);
}

function deleteResource(id) {
  let key = id.replace(/^resources\//, '');

  // Query deletes resouce, its nodes, and outgoing edges (but not incoming)
  return db.query(aql`
    LET res = FIRST(
      REMOVE { '_key': ${key} } IN resources
      RETURN OLD
    )
    LET nodes = (
      FOR node in graphNodes
        FILTER node['resource_id'] == res._id
        REMOVE node IN graphNodes
        RETURN OLD
    )
    FOR node IN nodes
      FOR edge IN edges
        FILTER edge['_from'] == node._id
        REMOVE edge IN edges
  `);
}

// "Delete" a part of a resource
// TODO: Not too sure I like how this function works or its name...
function deletePartialResource(id, path, doc) {
  let key = id.replace(/^resources\//, '');
  doc = doc || {};

  // Fix rev
  doc['_oada_rev'] = doc['_rev'];
  doc['_rev'] = undefined;

  pointer.set(doc, path, null);

  // Regex to match starts of paths
  let preg = `^${Array.isArray(path) ? pointer.compile(path) : path}(/|$)`;

  return db.query(aql`
    LET res = FIRST(
      UPDATE { '_key': ${key} }
      WITH ${doc}
      IN resources
      OPTIONS { keepNull: false }
      RETURN NEW
    )
    LET nodes = (
      FOR node IN graphNodes
        FILTER node['resource_id'] == res._id AND node.path =~ ${preg}
        REMOVE node IN graphNodes
        RETURN OLD
    )
    FOR node IN nodes
      FOR edge IN edges
        FILTER edge['_from'] == node._id
        REMOVE edge IN edges
  `);
}

module.exports = {
  lookupFromUrl,
  getResource,
  getResourceOwnerIdRev,
  putResource,
  deleteResource,
  deletePartialResource,
  getParents,
  // TODO: Better way to handler errors?
  // ErrorNum from: https://docs.arangodb.com/2.8/ErrorCodes/
  NotFoundError: {
    name: 'ArangoError',
    errorNum: 1202
  },
  UniqueConstraintError: {
    name: 'ArangoError',
    errorNum: 1210
  },
};
<|endoftext|>


<commit_before>module.exports = {
  title: 'Science Museum Group Collection',
  titlePage: 'Science Museum Group Collection',
  strap: 'Search our collection',
  intro: 'Explore over 250,000 objects and archives from the Science Museum, Museum of Science and Industry, National Science and Media Museum and National Railway Museum.',
  headerImages: [
    'handley.jpg',
    'textile-sign.jpg',
    'switches.jpg',
    '1878-0005_0004_A163_0002.jpg',
    'cd0634_030_100924_1988_242.jpg',
    'cd0274_008_070301.jpg'
  ],
  collectionGroup: [{
    title: 'See what’s on display',
    collection: [{
      type: 'collection',
      title: 'Science Museum',
      description: 'The home of human ingenuity, exploring stories of scientific achievement',
      figure: '/assets/img/home/collections/sciencemuseum.jpg',
      columns: 'small-6 medium-3',
      link: '/search/museum/science-museum'
    }, {
      type: 'collection',
      title: 'Museum of Science and Industry',
      figure: '/assets/img/home/collections/msi.jpg',
      description: 'The story of Manchester’s scientific and industrial past, present and future',
      columns: 'small-6 medium-3',
      link: '/search/museum/museum-of-science-and-industry'
    }, {
      type: 'collection',
      title: 'National Science and Media Museum',
      figure: '/assets/img/home/collections/nmm.jpg',
      description: 'The science and culture of light and sound technologies, and their impact on our lives',
      columns: 'small-6 medium-3',
      link: '/search/museum/national-media-museum'
    }, {
      type: 'collection',
      title: 'National Railway Museum',
      figure: '/assets/img/home/collections/nrm.jpg',
      description: 'Home to the largest collection of railway objects in the world',
      columns: 'small-6 medium-3',
      link: '/search/museum/national-railway-museum'
    }]
  }, {
    title: 'Themes from our collection',
    collection: [{
      type: 'collection',
      title: 'Information Age',
      description: 'Search across a snapshot of objects representing the development of the internet',
      figure: '/assets/img/home/collections/informationage.jpg',
      columns: 'small-6 medium-3',
      link: '/search/gallery/information-age-gallery%3A-web'
    }, {
      type: 'collection',
      title: 'Cinematography',
      figure: '/assets/img/home/collections/cinematography.jpg',
      description: 'Objects representing the history of cinema: from magic lanterns to digital technologies',
      columns: 'small-6 medium-3',
      link: '/search/categories/cinematography/images'
    }, {
      type: 'collection',
      title: 'The Babbage Papers',
      figure: '/assets/img/home/collections/babbage.jpg',
      description: 'The drawings and papers of mathematician and computer pioneer Charles Babbage',
      columns: 'small-6 medium-3',
      link: '/documents/aa110000003/the-babbage-papers'
    },
    {
      type: 'collection',
      title: 'Railway Photographic Collection',
      figure: '/assets/img/home/collections/railway-photographic-collections.jpg',
      description: 'Explore railway history--from technological to social change--using our photographic collection',
      columns: 'small-6 medium-3',
      link: '/search/categories/photographic-collections-(railway)'
    }
    /*
    {
      type: 'collection',
      title: 'Railway Posters',
      description: 'Explore our collection of historic railway posters',
      figure: '/assets/img/home/collections/railway-posters.jpg',
      columns: 'small-6 medium-3',
      link: '/search/images/categories/railway-posters,-notices-&-handbills'
    }
    */
    ]
  }]
};
<commit_msg>limits photo collection promo to colectionns with images<commit_after>module.exports = {
  title: 'Science Museum Group Collection',
  titlePage: 'Science Museum Group Collection',
  strap: 'Search our collection',
  intro: 'Explore over 250,000 objects and archives from the Science Museum, Museum of Science and Industry, National Science and Media Museum and National Railway Museum.',
  headerImages: [
    'handley.jpg',
    'textile-sign.jpg',
    'switches.jpg',
    '1878-0005_0004_A163_0002.jpg',
    'cd0634_030_100924_1988_242.jpg',
    'cd0274_008_070301.jpg'
  ],
  collectionGroup: [{
    title: 'See what’s on display',
    collection: [{
      type: 'collection',
      title: 'Science Museum',
      description: 'The home of human ingenuity, exploring stories of scientific achievement',
      figure: '/assets/img/home/collections/sciencemuseum.jpg',
      columns: 'small-6 medium-3',
      link: '/search/museum/science-museum'
    }, {
      type: 'collection',
      title: 'Museum of Science and Industry',
      figure: '/assets/img/home/collections/msi.jpg',
      description: 'The story of Manchester’s scientific and industrial past, present and future',
      columns: 'small-6 medium-3',
      link: '/search/museum/museum-of-science-and-industry'
    }, {
      type: 'collection',
      title: 'National Science and Media Museum',
      figure: '/assets/img/home/collections/nmm.jpg',
      description: 'The science and culture of light and sound technologies, and their impact on our lives',
      columns: 'small-6 medium-3',
      link: '/search/museum/national-media-museum'
    }, {
      type: 'collection',
      title: 'National Railway Museum',
      figure: '/assets/img/home/collections/nrm.jpg',
      description: 'Home to the largest collection of railway objects in the world',
      columns: 'small-6 medium-3',
      link: '/search/museum/national-railway-museum'
    }]
  }, {
    title: 'Themes from our collection',
    collection: [{
      type: 'collection',
      title: 'Information Age',
      description: 'Search across a snapshot of objects representing the development of the internet',
      figure: '/assets/img/home/collections/informationage.jpg',
      columns: 'small-6 medium-3',
      link: '/search/gallery/information-age-gallery%3A-web'
    }, {
      type: 'collection',
      title: 'Cinematography',
      figure: '/assets/img/home/collections/cinematography.jpg',
      description: 'Objects representing the history of cinema: from magic lanterns to digital technologies',
      columns: 'small-6 medium-3',
      link: '/search/categories/cinematography/images'
    }, {
      type: 'collection',
      title: 'The Babbage Papers',
      figure: '/assets/img/home/collections/babbage.jpg',
      description: 'The drawings and papers of mathematician and computer pioneer Charles Babbage',
      columns: 'small-6 medium-3',
      link: '/documents/aa110000003/the-babbage-papers'
    },
    {
      type: 'collection',
      title: 'Railway Photographic Collection',
      figure: '/assets/img/home/collections/railway-photographic-collections.jpg',
      description: 'Explore railway history--from technological to social change--using our photographic collection',
      columns: 'small-6 medium-3',
      link: '/search/categories/photographic-collections-(railway)/images'
    }
    /*
    {
      type: 'collection',
      title: 'Railway Posters',
      description: 'Explore our collection of historic railway posters',
      figure: '/assets/img/home/collections/railway-posters.jpg',
      columns: 'small-6 medium-3',
      link: '/search/images/categories/railway-posters,-notices-&-handbills'
    }
    */
    ]
  }]
};
<|endoftext|>


<commit_before>revyWeatherApp.controller('WebcamsController', ['$scope', '$mdDialog', function($scope, $mdDialog) {
    var time = new Date().getTime();
    $scope.webcams = [
        {
            title: 'Mt Mackenzie',
            url: 'courthouse.jpg?'+time
        },{
            title: 'Monashee Mountains',
            url: 'http://www.revelstokemountainresort.com/uploads/webcams/stoke.jpg?'+time
        },{
            title: 'Gnorm',
            url: 'http://www.revelstokemountainresort.com/uploads/webcams/gnorm.jpg?'+time
        },{
            title: 'Top of RMR Gondola',
            url: 'http://www.revelstokemountainresort.com/uploads/webcams/outpost.jpg?'+time
        },{
            title: 'Top of RMR Ripper',
            url: 'http://www.revelstokemountainresort.com/uploads/webcams/ripper.jpg?'+time
        },{
            title: 'Rogers Pass',
            url: 'http://www.pc.gc.ca/images/remotecamera/landscape.jpg?'+time
        },{
            title: 'Fidelity Snow Plot',
            url: 'http://www.pc.gc.ca/images/remotecamera/snowstake.jpg?'+time
        },{
            title: 'Revelstoke',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/11.jpg?'+time
        },{
            title: 'Highway 23 North/TCH',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/584.jpg?'+time
        },{
            title: 'Highway 23 South/TCH',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/585.jpg?'+time
        },{
            title: 'Highway 23 South/TCH',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/586.jpg?'+time
        },{
            title: 'Highway 23 South/TCH',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/587.jpg?'+time
        },{
            title: 'Clanwilliam Overpass - East',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/296.jpg?'+time
        },{
            title: 'Clanwilliam Overpass - West',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/517.jpg?'+time
        },{
            title: '3 Valley Gap',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/12.jpg?'+time
        },{
            title: 'Jack McDonald Snowshed',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/159.jpg?'+time
        },{
            title: 'Rogers Pass',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/101.jpg?'+time
        },{
            title: 'Donald Bridge - East',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/385.jpg?'+time
        },{
            title: 'Donald Bridge - West',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/386.jpg?'+time
        },{
            title: 'Golden',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/135.jpg?'+time
        },{
            title: 'Kicking Horse Canyon',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/214.jpg?'+time
        },{
            title: 'Field NE',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/142.jpg?'+time
        },{
            title: 'Field',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/189.jpg?'+time
        },{
            title: 'Lake Louise - West',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/218.jpg?'+time
        },{
            title: 'Lake Louise - East',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/219.jpg?'+time
        },{
            title: 'Banff - West',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/220.jpg?'+time
        },{
            title: 'Banff - East',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/221.jpg?'+time
        }
    ];

    $scope.expand = function(event, url) {
        $mdDialog.show({
            targetEvent: event,
            parent: angular.element(document.body),
            fullscreen: true,
            clickOutsideToClose: true,
            template:
                '<md-dialog aria-label="Fullscreen webcam" layout-fill>' +
                    '<md-dialog-actions>' +
                    '<md-button ng-click="closeDialog()" class="md-icon-button text-white" aria-label="Close">' +
                    '' +
                    '</md-button>' +
                    '</md-dialog-actions>' +
                    '<md-dialog-content>' +
                    '' +
                    '</md-dialog-content>' +
                '</md-dialog>',
            controller: DialogController
        }).catch(function(e) {
            console.log(e);
        });
    };

    function DialogController($scope, $mdDialog) {
        $scope.closeDialog = function() {
            $mdDialog.hide();
        }
    }
}]);<commit_msg>remove non updating RMR webcams<commit_after>revyWeatherApp.controller('WebcamsController', ['$scope', '$mdDialog', function($scope, $mdDialog) {
    var time = new Date().getTime();
    $scope.webcams = [
        {
            title: 'Mt Mackenzie',
            url: 'courthouse.jpg?'+time
        },{
            title: 'Monashee Mountains',
            url: 'http://www.revelstokemountainresort.com/uploads/webcams/stoke.jpg?'+time
        },{
            title: 'Top of RMR Gondola',
            url: 'http://www.revelstokemountainresort.com/uploads/webcams/outpost.jpg?'+time
        },{
            title: 'Rogers Pass',
            url: 'http://www.pc.gc.ca/images/remotecamera/landscape.jpg?'+time
        },{
            title: 'Fidelity Snow Plot',
            url: 'http://www.pc.gc.ca/images/remotecamera/snowstake.jpg?'+time
        },{
            title: 'Revelstoke',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/11.jpg?'+time
        },{
            title: 'Highway 23 North/TCH',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/584.jpg?'+time
        },{
            title: 'Highway 23 South/TCH',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/585.jpg?'+time
        },{
            title: 'Highway 23 South/TCH',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/586.jpg?'+time
        },{
            title: 'Highway 23 South/TCH',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/587.jpg?'+time
        },{
            title: 'Clanwilliam Overpass - East',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/296.jpg?'+time
        },{
            title: 'Clanwilliam Overpass - West',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/517.jpg?'+time
        },{
            title: '3 Valley Gap',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/12.jpg?'+time
        },{
            title: 'Jack McDonald Snowshed',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/159.jpg?'+time
        },{
            title: 'Rogers Pass',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/101.jpg?'+time
        },{
            title: 'Donald Bridge - East',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/385.jpg?'+time
        },{
            title: 'Donald Bridge - West',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/386.jpg?'+time
        },{
            title: 'Golden',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/135.jpg?'+time
        },{
            title: 'Kicking Horse Canyon',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/214.jpg?'+time
        },{
            title: 'Field NE',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/142.jpg?'+time
        },{
            title: 'Field',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/189.jpg?'+time
        },{
            title: 'Lake Louise - West',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/218.jpg?'+time
        },{
            title: 'Lake Louise - East',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/219.jpg?'+time
        },{
            title: 'Banff - West',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/220.jpg?'+time
        },{
            title: 'Banff - East',
            url: 'http://images.drivebc.ca/bchighwaycam/pub/cameras/221.jpg?'+time
        }
    ];

    $scope.expand = function(event, url) {
        $mdDialog.show({
            targetEvent: event,
            parent: angular.element(document.body),
            fullscreen: true,
            clickOutsideToClose: true,
            template:
                '<md-dialog aria-label="Fullscreen webcam" layout-fill>' +
                    '<md-dialog-actions>' +
                    '<md-button ng-click="closeDialog()" class="md-icon-button text-white" aria-label="Close">' +
                    '' +
                    '</md-button>' +
                    '</md-dialog-actions>' +
                    '<md-dialog-content>' +
                    '' +
                    '</md-dialog-content>' +
                '</md-dialog>',
            controller: DialogController
        }).catch(function(e) {
            console.log(e);
        });
    };

    function DialogController($scope, $mdDialog) {
        $scope.closeDialog = function() {
            $mdDialog.hide();
        }
    }
}]);<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*global define, $, localStorage, brackets */
/*unittests: Preferences Manager */

/**
 * PreferencesManager
 *
 */
define(function (require, exports, module) {
    "use strict";

    var OldPreferenceStorage = require("preferences/PreferenceStorage").PreferenceStorage,
        FileUtils         = require("file/FileUtils"),
        ExtensionLoader   = require("utils/ExtensionLoader"),
        PreferencesBase   = require("preferences/PreferencesBase"),
        FileSystem        = require("filesystem/FileSystem"),
        _                 = require("thirdparty/lodash");

    /**
     * The local storage ID
     * @const
     * @type {string}
     */
    var PREFERENCES_CLIENT_ID = "com.adobe.brackets.preferences";

    /**
     * The prefix used in the generated client ID
     * @const
     * @type {string}
     */
    var CLIENT_ID_PREFIX = "com.adobe.brackets.";

    // Private Properties
    var preferencesKey,
        prefStorage,
        persistentStorage,
        extensionPaths,
        doLoadPreferences   = false;

    /**
     * @private
     * Returns an array with the extension paths used in Brackets. The result is stored on a
     * private variable on the first call and used to return the value on the next calls.
     * @return {Array.<string>}
     */
    function _getExtensionPaths() {
        if (!extensionPaths) {
            var dirPath = FileUtils.getNativeBracketsDirectoryPath();

            extensionPaths = [
                dirPath + "/extensions/default/",
                dirPath + "/extensions/dev/",
                ExtensionLoader.getUserExtensionPath() + "/"
            ];
        }
        return extensionPaths;
    }

    /**
     * This method returns a standardized ClientID for a given requireJS module object
     * @param {!{id: string, uri: string}} module - A requireJS module object
     * @return {string} The ClientID
     */
    function getClientID(module) {
        var paths = exports._getExtensionPaths();
        var pathExp, pathUrl, clientID;

        paths.some(function (path) {
            if (module.uri.toLocaleLowerCase().indexOf(path.toLocaleLowerCase()) === 0) {
                pathUrl = path;
                return true;
            }
        });

        if (pathUrl) {
            clientID = CLIENT_ID_PREFIX + module.uri.replace(pathUrl, "");
        } else {
            clientID = CLIENT_ID_PREFIX + module.id;
        }
        return clientID;
    }

    /**
     * Retreive the preferences data for the given clientID.
     * @param {string|{id: string, uri: string}} clientID - A unique identifier or a requireJS module object
     * @param {string} defaults - Default preferences stored as JSON
     * @return {PreferenceStorage}
     */
    function getPreferenceStorage(clientID, defaults) {
        if (!clientID || (typeof clientID === "object" && (!clientID.id || !clientID.uri))) {
            console.error("Invalid clientID");
            return;
        }
        if (typeof clientID === "object") {
            clientID = getClientID(clientID);
        }

        var prefs = prefStorage[clientID];

        if (prefs === undefined) {
            // create a new empty preferences object
            prefs = (defaults && JSON.stringify(defaults)) ? defaults : {};
            prefStorage[clientID] = prefs;
        } else if (defaults) {
            // add new defaults
            _.forEach(defaults, function (value, key) {
                if (prefs[key] === undefined) {
                    prefs[key] = value;
                }
            });
        }

        return new OldPreferenceStorage(clientID, prefs);
    }

    /**
     * Save all preference clients.
     */
    function savePreferences() {
        // save all preferences
        persistentStorage.setItem(preferencesKey, JSON.stringify(prefStorage));
    }

    /**
     * @private
     * Reset preferences and callbacks
     */
    function _reset() {
        prefStorage = {};

        // Note that storage.clear() is not used. Production and unit test code
        // both rely on the same backing storage but unique item keys.
        persistentStorage.setItem(preferencesKey, JSON.stringify(prefStorage));
    }

    /**
     * @private
     * Initialize persistent storage implementation
     */
    function _initStorage(storage) {
        persistentStorage = storage;

        if (doLoadPreferences) {
            prefStorage = JSON.parse(persistentStorage.getItem(preferencesKey));
        }

        // initialize empty preferences if none were found in storage
        if (!prefStorage) {
            _reset();
        }
    }

    // Check localStorage for a preferencesKey. Production and unit test keys
    // are used to keep preferences separate within the same storage implementation.
    preferencesKey = localStorage.getItem("preferencesKey");

    if (!preferencesKey) {
        // use default key if none is found
        preferencesKey = PREFERENCES_CLIENT_ID;
        doLoadPreferences = true;
    } else {
        // using a non-default key, check for additional settings
        doLoadPreferences = !!(localStorage.getItem("doLoadPreferences"));
    }

    // Use localStorage by default
    _initStorage(localStorage);

    // Public API
    exports.getPreferenceStorage    = getPreferenceStorage;
    exports.savePreferences         = savePreferences;
    exports.getClientID             = getClientID;

    // Unit test use only
    exports._reset                  = _reset;
    exports._getExtensionPaths      = _getExtensionPaths;

    // New code follows. The code above (with the exception of the imports) is
    // deprecated.

    // The SETTINGS_FILENAME is used with a preceding "." within user projects
    var SETTINGS_FILENAME = "brackets.json",
        STATE_FILENAME    = "state.json";

    var preferencesManager = new PreferencesBase.PreferencesManager();

    // User-level preferences
    var userPrefFile = brackets.app.getApplicationSupportDirectory() + "/" + SETTINGS_FILENAME;

    preferencesManager.addScope("user", new PreferencesBase.FileStorage(userPrefFile, true));

    // Set up the .brackets.json file handling
    preferencesManager.addPathScopes(".brackets.json", {
        before: "user",
        checkExists: function (filename) {
            var result = new $.Deferred(),
                file = FileSystem.getFileForPath(filename);
            file.exists(function (err, doesExist) {
                result.resolve(doesExist);
            });
            return result.promise();
        },
        getScopeForFile: function (filename) {
            return new PreferencesBase.Scope(new PreferencesBase.FileStorage(filename));
        }
    });

    // "State" is stored like preferences but it is not generally intended to be user-editable.
    // It's for more internal, implicit things like window size, working set, etc.
    var stateManager = new PreferencesBase.PreferencesManager();
    var userStateFile = brackets.app.getApplicationSupportDirectory() + "/" + STATE_FILENAME;

    stateManager.addScope("user", new PreferencesBase.FileStorage(userStateFile, true));

    // Convenience function that sets a preference and then saves the file, mimicking the
    // old behavior a bit more closely.
    function setValueAndSave(scopeName, id, value) {
        preferencesManager.set(scopeName, id, value);
        preferencesManager.save();
    }

    // Private API for unit testing and use elsewhere in Brackets core
    exports._manager = preferencesManager;
    exports._setCurrentEditingFile = preferencesManager.setPathScopeContext.bind(preferencesManager);

    // Public API

    exports.get = preferencesManager.get.bind(preferencesManager);
    exports.set = preferencesManager.set.bind(preferencesManager);
    exports.save = preferencesManager.save.bind(preferencesManager);
    exports.on = preferencesManager.on.bind(preferencesManager);
    exports.off = preferencesManager.off.bind(preferencesManager);
    exports.getPreference = preferencesManager.getPreference.bind(preferencesManager);
    exports.setValueAndSave = setValueAndSave;
    exports.addScope = preferencesManager.addScope.bind(preferencesManager);
    exports.stateManager = stateManager;
    exports.FileStorage = PreferencesBase.FileStorage;
    exports.SETTINGS_FILENAME = SETTINGS_FILENAME;
    exports.definePreference = preferencesManager.definePreference.bind(preferencesManager);
    exports.fileChanged = preferencesManager.fileChanged.bind(preferencesManager);
});
<commit_msg>Fix jshint error<commit_after>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*global define, $, localStorage, brackets, console */
/*unittests: Preferences Manager */

/**
 * PreferencesManager
 *
 */
define(function (require, exports, module) {
    "use strict";

    var OldPreferenceStorage = require("preferences/PreferenceStorage").PreferenceStorage,
        FileUtils         = require("file/FileUtils"),
        ExtensionLoader   = require("utils/ExtensionLoader"),
        PreferencesBase   = require("preferences/PreferencesBase"),
        FileSystem        = require("filesystem/FileSystem"),
        _                 = require("thirdparty/lodash");

    /**
     * The local storage ID
     * @const
     * @type {string}
     */
    var PREFERENCES_CLIENT_ID = "com.adobe.brackets.preferences";

    /**
     * The prefix used in the generated client ID
     * @const
     * @type {string}
     */
    var CLIENT_ID_PREFIX = "com.adobe.brackets.";

    // Private Properties
    var preferencesKey,
        prefStorage,
        persistentStorage,
        extensionPaths,
        doLoadPreferences   = false;

    /**
     * @private
     * Returns an array with the extension paths used in Brackets. The result is stored on a
     * private variable on the first call and used to return the value on the next calls.
     * @return {Array.<string>}
     */
    function _getExtensionPaths() {
        if (!extensionPaths) {
            var dirPath = FileUtils.getNativeBracketsDirectoryPath();

            extensionPaths = [
                dirPath + "/extensions/default/",
                dirPath + "/extensions/dev/",
                ExtensionLoader.getUserExtensionPath() + "/"
            ];
        }
        return extensionPaths;
    }

    /**
     * This method returns a standardized ClientID for a given requireJS module object
     * @param {!{id: string, uri: string}} module - A requireJS module object
     * @return {string} The ClientID
     */
    function getClientID(module) {
        var paths = exports._getExtensionPaths();
        var pathExp, pathUrl, clientID;

        paths.some(function (path) {
            if (module.uri.toLocaleLowerCase().indexOf(path.toLocaleLowerCase()) === 0) {
                pathUrl = path;
                return true;
            }
        });

        if (pathUrl) {
            clientID = CLIENT_ID_PREFIX + module.uri.replace(pathUrl, "");
        } else {
            clientID = CLIENT_ID_PREFIX + module.id;
        }
        return clientID;
    }

    /**
     * Retreive the preferences data for the given clientID.
     * @param {string|{id: string, uri: string}} clientID - A unique identifier or a requireJS module object
     * @param {string} defaults - Default preferences stored as JSON
     * @return {PreferenceStorage}
     */
    function getPreferenceStorage(clientID, defaults) {
        if (!clientID || (typeof clientID === "object" && (!clientID.id || !clientID.uri))) {
            console.error("Invalid clientID");
            return;
        }
        if (typeof clientID === "object") {
            clientID = getClientID(clientID);
        }

        var prefs = prefStorage[clientID];

        if (prefs === undefined) {
            // create a new empty preferences object
            prefs = (defaults && JSON.stringify(defaults)) ? defaults : {};
            prefStorage[clientID] = prefs;
        } else if (defaults) {
            // add new defaults
            _.forEach(defaults, function (value, key) {
                if (prefs[key] === undefined) {
                    prefs[key] = value;
                }
            });
        }

        return new OldPreferenceStorage(clientID, prefs);
    }

    /**
     * Save all preference clients.
     */
    function savePreferences() {
        // save all preferences
        persistentStorage.setItem(preferencesKey, JSON.stringify(prefStorage));
    }

    /**
     * @private
     * Reset preferences and callbacks
     */
    function _reset() {
        prefStorage = {};

        // Note that storage.clear() is not used. Production and unit test code
        // both rely on the same backing storage but unique item keys.
        persistentStorage.setItem(preferencesKey, JSON.stringify(prefStorage));
    }

    /**
     * @private
     * Initialize persistent storage implementation
     */
    function _initStorage(storage) {
        persistentStorage = storage;

        if (doLoadPreferences) {
            prefStorage = JSON.parse(persistentStorage.getItem(preferencesKey));
        }

        // initialize empty preferences if none were found in storage
        if (!prefStorage) {
            _reset();
        }
    }

    // Check localStorage for a preferencesKey. Production and unit test keys
    // are used to keep preferences separate within the same storage implementation.
    preferencesKey = localStorage.getItem("preferencesKey");

    if (!preferencesKey) {
        // use default key if none is found
        preferencesKey = PREFERENCES_CLIENT_ID;
        doLoadPreferences = true;
    } else {
        // using a non-default key, check for additional settings
        doLoadPreferences = !!(localStorage.getItem("doLoadPreferences"));
    }

    // Use localStorage by default
    _initStorage(localStorage);

    // Public API
    exports.getPreferenceStorage    = getPreferenceStorage;
    exports.savePreferences         = savePreferences;
    exports.getClientID             = getClientID;

    // Unit test use only
    exports._reset                  = _reset;
    exports._getExtensionPaths      = _getExtensionPaths;

    // New code follows. The code above (with the exception of the imports) is
    // deprecated.

    // The SETTINGS_FILENAME is used with a preceding "." within user projects
    var SETTINGS_FILENAME = "brackets.json",
        STATE_FILENAME    = "state.json";

    var preferencesManager = new PreferencesBase.PreferencesManager();

    // User-level preferences
    var userPrefFile = brackets.app.getApplicationSupportDirectory() + "/" + SETTINGS_FILENAME;

    preferencesManager.addScope("user", new PreferencesBase.FileStorage(userPrefFile, true));

    // Set up the .brackets.json file handling
    preferencesManager.addPathScopes(".brackets.json", {
        before: "user",
        checkExists: function (filename) {
            var result = new $.Deferred(),
                file = FileSystem.getFileForPath(filename);
            file.exists(function (err, doesExist) {
                result.resolve(doesExist);
            });
            return result.promise();
        },
        getScopeForFile: function (filename) {
            return new PreferencesBase.Scope(new PreferencesBase.FileStorage(filename));
        }
    });

    // "State" is stored like preferences but it is not generally intended to be user-editable.
    // It's for more internal, implicit things like window size, working set, etc.
    var stateManager = new PreferencesBase.PreferencesManager();
    var userStateFile = brackets.app.getApplicationSupportDirectory() + "/" + STATE_FILENAME;

    stateManager.addScope("user", new PreferencesBase.FileStorage(userStateFile, true));

    // Convenience function that sets a preference and then saves the file, mimicking the
    // old behavior a bit more closely.
    function setValueAndSave(scopeName, id, value) {
        preferencesManager.set(scopeName, id, value);
        preferencesManager.save();
    }

    // Private API for unit testing and use elsewhere in Brackets core
    exports._manager = preferencesManager;
    exports._setCurrentEditingFile = preferencesManager.setPathScopeContext.bind(preferencesManager);

    // Public API

    exports.get = preferencesManager.get.bind(preferencesManager);
    exports.set = preferencesManager.set.bind(preferencesManager);
    exports.save = preferencesManager.save.bind(preferencesManager);
    exports.on = preferencesManager.on.bind(preferencesManager);
    exports.off = preferencesManager.off.bind(preferencesManager);
    exports.getPreference = preferencesManager.getPreference.bind(preferencesManager);
    exports.setValueAndSave = setValueAndSave;
    exports.addScope = preferencesManager.addScope.bind(preferencesManager);
    exports.stateManager = stateManager;
    exports.FileStorage = PreferencesBase.FileStorage;
    exports.SETTINGS_FILENAME = SETTINGS_FILENAME;
    exports.definePreference = preferencesManager.definePreference.bind(preferencesManager);
    exports.fileChanged = preferencesManager.fileChanged.bind(preferencesManager);
});
<|endoftext|>


<commit_before>'use strict';

// MODULES //

var tape = require( 'tape' );
var proxyquire = require( 'proxyquire' );
var isStringArray = require( '@stdlib/assert/is-string-array' ).primitives;
var isArray = require( '@stdlib/assert/is-array' );
var inspect = require( './../lib/inspect.js' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.strictEqual( typeof inspect, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function returns a string array if able to detect at least one packages containing WebAssembly', function test( t ) {
	var inspect;
	var count;

	inspect = proxyquire( './../lib/inspect.js', {
		'@stdlib/fs/read-json': readJSON
	});
	count = 0;

	inspect( [ '/beep/boop/package.json', '/a/b/c/d/e/f/g/h/beep/boop/bop/package.json' ], clbk );

	function readJSON() {
		var clbk = arguments[ arguments.length-1 ];
		var pkg;

		count += 1;
		if ( count < 2 ) {
			pkg = {
				'__stdlib__': {
					'wasm': true
				}
			};
		} else {
			pkg = {
				'__stdlib__': {
					'wasm': false
				}
			};
		}
		clbk( null, pkg );
	}

	function clbk( error, pkgs ) {
		if ( error ) {
			t.ok( false, error.message );
		}
		t.strictEqual( isStringArray( pkgs ), true, 'returns a string array' );
		t.strictEqual( pkgs.length, 1, 'expected length' );
		t.end();
	}
});

tape( 'the function returns an empty array if unable to detect at least one package containing WebAssembly', function test( t ) {
	var inspect = proxyquire( './../lib/inspect.js', {
		'@stdlib/fs/read-json': readJSON
	});

	inspect( [ '/a/b/c/d/e/f/g/h/beep/boop/bop/package.json' ], clbk );

	function readJSON() {
		var clbk = arguments[ arguments.length-1 ];
		clbk( null, {
			'_stdlib__': false
		});
	}

	function clbk( error, pkgs ) {
		if ( error ) {
			t.ok( false, error.message );
		}
		t.strictEqual( isArray( pkgs ), true, 'returns an array' );
		t.strictEqual( pkgs.length, 0, 'returns an empty array' );
		t.end();
	}
});

tape( 'the function returns an empty array if unable to detect at least one package containing WebAssembly (error)', function test( t ) {
	var inspect = proxyquire( './../lib/inspect.js', {
		'@stdlib/fs/read-json': readJSON
	});

	inspect( [ '/a/b/c/d/e/f/g/h/beep/boop/bop/package.json' ], clbk );

	function readJSON() {
		var clbk = arguments[ arguments.length-1 ];
		clbk( new Error( 'beep' ) );
	}

	function clbk( error, pkgs ) {
		if ( error ) {
			t.ok( false, error.message );
		}
		t.strictEqual( isArray( pkgs ), true, 'returns an array' );
		t.strictEqual( pkgs.length, 0, 'returns an empty array' );
		t.end();
	}
});

tape( 'the function returns an empty array if unable to detect at least one package containing WebAssembly (no error)', function test( t ) {
	var inspect = proxyquire( './../lib/inspect.js', {
		'@stdlib/fs/read-json': readJSON
	});

	inspect( [ '/a/b/c/d/e/f/g/h/beep/boop/bop/package.json' ], clbk );

	function readJSON() {
		var clbk = arguments[ arguments.length-1 ];
		clbk( null, {} );
	}

	function clbk( error, pkgs ) {
		if ( error ) {
			t.ok( false, error.message );
		}
		t.strictEqual( isArray( pkgs ), true, 'returns an array' );
		t.strictEqual( pkgs.length, 0, 'returns an empty array' );
		t.end();
	}
});
<commit_msg>Fix misspelling<commit_after>'use strict';

// MODULES //

var tape = require( 'tape' );
var proxyquire = require( 'proxyquire' );
var isStringArray = require( '@stdlib/assert/is-string-array' ).primitives;
var isArray = require( '@stdlib/assert/is-array' );
var inspect = require( './../lib/inspect.js' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( true, __filename );
	t.strictEqual( typeof inspect, 'function', 'main export is a function' );
	t.end();
});

tape( 'the function returns a string array if able to detect at least one package containing WebAssembly', function test( t ) {
	var inspect;
	var count;

	inspect = proxyquire( './../lib/inspect.js', {
		'@stdlib/fs/read-json': readJSON
	});
	count = 0;

	inspect( [ '/beep/boop/package.json', '/a/b/c/d/e/f/g/h/beep/boop/bop/package.json' ], clbk );

	function readJSON() {
		var clbk = arguments[ arguments.length-1 ];
		var pkg;

		count += 1;
		if ( count < 2 ) {
			pkg = {
				'__stdlib__': {
					'wasm': true
				}
			};
		} else {
			pkg = {
				'__stdlib__': {
					'wasm': false
				}
			};
		}
		clbk( null, pkg );
	}

	function clbk( error, pkgs ) {
		if ( error ) {
			t.ok( false, error.message );
		}
		t.strictEqual( isStringArray( pkgs ), true, 'returns a string array' );
		t.strictEqual( pkgs.length, 1, 'expected length' );
		t.end();
	}
});

tape( 'the function returns an empty array if unable to detect at least one package containing WebAssembly', function test( t ) {
	var inspect = proxyquire( './../lib/inspect.js', {
		'@stdlib/fs/read-json': readJSON
	});

	inspect( [ '/a/b/c/d/e/f/g/h/beep/boop/bop/package.json' ], clbk );

	function readJSON() {
		var clbk = arguments[ arguments.length-1 ];
		clbk( null, {
			'_stdlib__': false
		});
	}

	function clbk( error, pkgs ) {
		if ( error ) {
			t.ok( false, error.message );
		}
		t.strictEqual( isArray( pkgs ), true, 'returns an array' );
		t.strictEqual( pkgs.length, 0, 'returns an empty array' );
		t.end();
	}
});

tape( 'the function returns an empty array if unable to detect at least one package containing WebAssembly (error)', function test( t ) {
	var inspect = proxyquire( './../lib/inspect.js', {
		'@stdlib/fs/read-json': readJSON
	});

	inspect( [ '/a/b/c/d/e/f/g/h/beep/boop/bop/package.json' ], clbk );

	function readJSON() {
		var clbk = arguments[ arguments.length-1 ];
		clbk( new Error( 'beep' ) );
	}

	function clbk( error, pkgs ) {
		if ( error ) {
			t.ok( false, error.message );
		}
		t.strictEqual( isArray( pkgs ), true, 'returns an array' );
		t.strictEqual( pkgs.length, 0, 'returns an empty array' );
		t.end();
	}
});

tape( 'the function returns an empty array if unable to detect at least one package containing WebAssembly (no error)', function test( t ) {
	var inspect = proxyquire( './../lib/inspect.js', {
		'@stdlib/fs/read-json': readJSON
	});

	inspect( [ '/a/b/c/d/e/f/g/h/beep/boop/bop/package.json' ], clbk );

	function readJSON() {
		var clbk = arguments[ arguments.length-1 ];
		clbk( null, {} );
	}

	function clbk( error, pkgs ) {
		if ( error ) {
			t.ok( false, error.message );
		}
		t.strictEqual( isArray( pkgs ), true, 'returns an array' );
		t.strictEqual( pkgs.length, 0, 'returns an empty array' );
		t.end();
	}
});
<|endoftext|>