<commit_before>var roleArchitect = require('role.architect');
var bodyObj = require('module.bodyTypes');
var modCommon = require('module.common');

var modSpawning = {
  incrementNum:function(creepType){
    if(creepType==="harvester"){
      ++Memory.roles.numHarvesters;
    }else if(creepType === "upgrader"){
      ++Memory.roles.numUpgraders;
    }else if(creepType === "builder"){
      ++Memory.roles.numBuilders;
    }else if(creepType === "repair"){
      ++Memory.roles.numRepair;
    }else if (creepType === "architect"){
      ++Memory.roles.numArchitects;
    }else if(creepType === "miner"){
      ++Memory.roles.numMiners;
    }else if(creepType === "hauler"){
      ++Memory.roles.numHaulers;
    }else if(creepType === "feeder"){
      ++Memory.roles.numFeeders;
    }else if(creepType === "geo"){
      ++Memory.roles.numGeo;
    }else if(creepType === "geoHauler"){
      ++Memory.roles.numGeoH;
    }else if(creepType === "merchant"){
      ++Memory.roles.numMerchant;
    }
    ++Memory.roles.numCreeps;
  },

  spawn:function(spawner,energyCapacity,controllerLvl){
    //TODO put all of the constants here in memory after first run
    //Maybe an init method for the module
    var numSources = spawner.room.find(FIND_SOURCES).length;
    var numTowers = spawner.room.find(FIND_STRUCTURES, {filter: (struct)=> struct.structureType === STRUCTURE_TOWER}).length;
    var isStorage = (spawner.room.storage !== undefined) && (spawner.room.storage.store.energy > 1000);
    var betterHarvesters1 = Memory.roles.betterHarvesters1; // number of tier2 harvesters spawned, to track shift

    var betterHarvesters2 = Memory.roles.betterHarvesters2; // number of tier2 harvesters spawned, to track shift
    var controllerLvlMod = Math.ceil((controllerLvl-1)/3);  //modifier to the max amount of each creep

    var notEnoughHarvest = ((Memory.roles.numHarvesters < Memory.roles.maxHarvesters) && (Memory.roles.numMiners + Memory.roles.numHarvesters < 1));  //if there are enough harvesters, to stop other higher tier spawns

    var noHarvest = (Memory.roles.numHarvesters < 1) && (Memory.roles.numHaulers<1);

    var spawnTier1 = (energyCapacity<550 || (spawner.room.energyAvailable<550 && noHarvest)); //boolean for tier 1 spawning

    var spawnTier2 = (energyCapacity<800 || (spawner.room.energyAvailable<800 && notEnoughHarvest));  //boolean for tier2 spawning

    var spawnTier3 = (energyCapacity<1300 || (spawner.room.energyAvailable<800 && notEnoughHarvest));  //boolean for tier3 spawning

    var spawnTier4 = (energyCapacity>=1300);

    var flags = _.filter(spawner.room.find(FIND_FLAGS), (flag) => roleArchitect.ableToBuild(controllerLvl, flag.name));

    var body = [MOVE];

    var extractor = _.filter(spawner.room.find(FIND_STRUCTURES), (structure) => structure.structureType === STRUCTURE_EXTRACTOR);

    var market = _.filter(spawner.room.find(FIND_STRUCTURES), (structure) => structure.structureType === STRUCTURE_TERMINAL);

    var minerals = (_.filter(spawner.room.find(FIND_MINERALS), (mineral) => mineral.mineralAmount > 0).length) > 0;

    var toSell = false;//!minerals && (modCommon.whatStore(spawner.room.storage) != RESOURCE_ENERGY);

    if(spawnTier1){
        //make sure there is energy and the spawner isnt already working, then spawn harvesters, upgrader, and repair in that priority
        if((!spawner.spawning) && (spawner.room.energyAvailable>=300)){
            if(Memory.roles.numHarvesters < (Memory.roles.maxHarvesters)){
              body = bodyObj.getBody('harvester',1);
              var hc = spawner.createCreep(body, undefined,{role: 'harvester',selfHarvest:true, source:""});
              console.log("Spawning Harvester: " + hc);
              this.incrementNum("harvester");
            }
            else if(Memory.roles.numUpgraders < Math.min(Memory.roles.maxUpgraders+controllerLvlMod, spawner.room.find(FIND_SOURCES).length)){
              body = bodyObj.getBody('upgrader',1);
              var uc = spawner.createCreep(body, undefined,{role: 'upgrader', selfHarvest:true});
              console.log("Spawning Upgrader: " + uc);
              this.incrementNum("upgrader");
            }
            else if(Memory.roles.numRepair< (Memory.roles.maxRepair - numTowers)){
              body = bodyObj.getBody('repair',1);
              var rc = spawner.createCreep(body, undefined,{role: 'repair', toFix:'', selfHarvest:true, military:true});
              console.log("Spawning Repair: " + rc);
              this.incrementNum("repair");
            }
            else if(flags.length && Memory.roles.numArchitects < 1){
              body = bodyObj.getBody('architect',1);
              var ac = spawner.createCreep(body, undefined,{role: 'architect', toFix:'', selfHarvest:false});
              console.log("Spawning Architect: " + ac);
              this.incrementNum("architect");
            }
        }
    }else if(spawnTier2){
      //make sure there is energy and the spawner isnt already working, then spawn harvesters, builders, upgrader, and repair in that priority
      var moveOn = (betterHarvesters1>=(Memory.roles.maxHarvesters));
      if((!spawner.spawning) && (spawner.room.energyAvailable>=550)){
          if(Memory.roles.numHarvesters < (Memory.roles.maxHarvesters)){
            body = bodyObj.getBody('harvester',2);
            var h2c = spawner.createCreep(body, undefined,{role: 'harvester',selfHarvest:true, source:""});
             console.log("Spawning Harvester: " + h2c);
             betterHarvesters1++;
             this.incrementNum("harvester");
          }
          else if(moveOn && spawner.room.find(FIND_MY_CONSTRUCTION_SITES).length && (Memory.roles.numBuilders < (Memory.roles.maxBuilders)) && Memory.roles.numArchitects === 0){
            body = bodyObj.getBody('builder',2);
            var b2c = spawner.createCreep(body, undefined,{role: 'builder',selfHarvest:false});
            console.log("Spawning Builder: " + b2c);
            this.incrementNum("builder");
          }
          else if(moveOn && Memory.roles.numUpgraders < Math.min(Memory.roles.maxUpgraders+controllerLvlMod, spawner.room.find(FIND_SOURCES).length)){
            body = bodyObj.getBody('upgrader',2);
            var u2c = spawner.createCreep(body, undefined,{role: 'upgrader', selfHarvest:false});
            console.log("Spawning Upgrader: " + u2c);
            this.incrementNum("upgrader");
          }
          else if(moveOn && Memory.roles.numRepair< (Memory.roles.maxRepair - numTowers)){
            body = bodyObj.getBody('repair',2);
            var r2c = spawner.createCreep(body, undefined,{role: 'repair', toFix:'', selfHarvest:true, military:true});
            console.log("Spawning Repair: " + r2c);
            this.incrementNum("repair");
          }
          else if(moveOn && flags.length && Memory.roles.numArchitects < 1){
            body = bodyObj.getBody('architect',2);
            var a2c = spawner.createCreep(body, undefined,{role: 'architect', toFix:'', selfHarvest:false});
            console.log("Spawning Architect: " + a2c);
            this.incrementNum("architect");
          }
      }
    }else if(spawnTier3){
      //make sure there is energy and the spawner isnt already working, then spawn harvesters, builders, upgrader, and repair in that priority
      var moveOn2 = (betterHarvesters2>=(Memory.roles.maxHarvesters));
      if((!spawner.spawning) && (spawner.room.energyAvailable>=800)){
        if(Memory.roles.numHarvesters < (Memory.roles.maxHarvesters)){
          body = bodyObj.getBody('harvester',3);
          var h3c = spawner.createCreep(body, undefined,{role: 'harvester',selfHarvest:true, source:""});
          console.log("Spawning Harvester: " + h3c);
          betterHarvesters2++;
          this.incrementNum("harvester");
        }
        else if(moveOn2 && spawner.room.find(FIND_MY_CONSTRUCTION_SITES).length && (Memory.roles.numBuilders < (Memory.roles.maxBuilders)) && Memory.roles.numArchitects === 0){
          body = bodyObj.getBody('builder',3);
          var b3c = spawner.createCreep(body, undefined,{role: 'builder',selfHarvest:false});
          console.log("Spawning Builder: " + b3c);
          this.incrementNum("builder");
        }
        else if(moveOn2 && Memory.roles.numUpgraders < Math.min(Memory.roles.maxUpgraders+controllerLvlMod, spawner.room.find(FIND_SOURCES).length)){
          body = bodyObj.getBody('upgrader',3);
          var u3c = spawner.createCreep(body, undefined,{role: 'upgrader', selfHarvest:false});
          console.log("Spawning Upgrader: " + u3c);
          this.incrementNum("upgrader");
        }
        else if(moveOn2 && Memory.roles.numRepair< (Memory.roles.maxRepair - numTowers)){
          body = bodyObj.getBody('repair',3);
          var r3c = spawner.createCreep(body, undefined,{role: 'repair', toFix:'', selfHarvest:true, military:true});
          console.log("Spawning Repair: " + r3c);
          this.incrementNum("repair");
        }
        else if(moveOn2 && flags.length && Memory.roles.numArchitects < 1){
          body = bodyObj.getBody('architect',3);
          var a3c = spawner.createCreep(body, undefined,{role: 'architect', toFix:'', selfHarvest:false});
          console.log("Spawning Architect: " + a3c);
          this.incrementNum("architect");
        }
      }
    }else if (spawnTier4){
      if(!spawner.spawning && (spawner.room.energyAvailable >=800)){
        var moveOn3 = (Memory.roles.numMiners >= numSources) && (Memory.roles.numHaulers>=1);
        if(Memory.roles.numMiners< numSources){
          body = bodyObj.getBody('miner',4);
          var mine = spawner.createCreep(body, undefined,{role: 'miner',source:"", dropOff: "", military:true});
          console.log("Spawning Miner: " + mine);
          this.incrementNum("miner");
        }
        else if(Memory.roles.numHaulers<Memory.roles.maxHaulers){
          body = bodyObj.getBody('hauler',4);
          var haul = spawner.createCreep(body, undefined,{role: 'hauler', military:true, dropOff:""});
          console.log("Spawning Hauler: " + haul);
          this.incrementNum("hauler");
        }
        else if(moveOn3 && Memory.roles.numFeeders<Memory.roles.maxFeeders && isStorage){
          body = bodyObj.getBody('feeder',4);
          var feed = spawner.createCreep(body, undefined,{role: 'feeder', military:true});
          console.log("Spawning Feeder: " + feed);
          this.incrementNum("feeder");
        }
        else if(moveOn3 && spawner.room.find(FIND_MY_CONSTRUCTION_SITES).length && (Memory.roles.numBuilders < (Memory.roles.maxBuilders)) && Memory.roles.numArchitects === 0){
          body = bodyObj.getBody('builder',4);
          var b4c = spawner.createCreep(body, undefined,{role: 'builder',selfHarvest:false});
          console.log("Spawning Builder: " + b4c);
          this.incrementNum("builder");
        }
        else if(moveOn3 && Memory.roles.numUpgraders < 1){
          body = bodyObj.getBody('upgrader',4);
          var u4c = spawner.createCreep(body, undefined,{role: 'upgrader', selfHarvest:false});
          console.log("Spawning Upgrader: " + u4c);
          this.incrementNum("upgrader");
        }
        else if(moveOn3 && Memory.roles.numRepair< (Memory.roles.maxRepair - numTowers)){
          body = bodyObj.getBody('repair',4);
          var r4c = spawner.createCreep(body, undefined,{role: 'repair', toFix:'', selfHarvest:false, military:true});
          console.log("Spawning Repair: " + r4c);
          this.incrementNum("repair");
        }
        else if(moveOn3 && flags.length && Memory.roles.numArchitects < 1){
          body = bodyObj.getBody('architect',4);
          var a4c = spawner.createCreep(body, undefined,{role: 'architect', toFix:'', selfHarvest:false});
          console.log("Spawning Architect: " + a4c);
          this.incrementNum("architect");
        }
        else if(moveOn3 && extractor && minerals && Memory.roles.numGeo < Memory.roles.maxGeo){
          body = bodyObj.getBody('geoMiner',4);
          var g4c = spawner.createCreep(body, undefined,{role: 'geo', mineral:"", dropOff:""});
          console.log("Spawning GeoMiner: " + g4c);
          this.incrementNum("geo");
        }
        else if(moveOn3 && extractor && Memory.roles.numGeoH < Memory.roles.maxGeoH){
          body = bodyObj.getBody('geoHauler',4);
          var gh4c = spawner.createCreep(body, undefined,{role: 'geoH', dropOff:""});
          console.log("Spawning GeoHauler: " + gh4c);
          this.incrementNum("geoHauler");
        }
        else if(moveOn3 && market && toSell && Memory.roles.numMerchant < Memory.roles.maxMerchant){
          body = bodyObj.getBody('merchant',4);
          var mer4c = spawner.createCreep(body, undefined,{role: 'merchant', terminal:"", storage:""});
          console.log("Spawning Merchant: " + mer4c);
          this.incrementNum("merchant");
        }
      }
    }
    Memory.roles.betterHarvesters1 = betterHarvesters1;
    Memory.roles.betterHarvesters2 = betterHarvesters2;

  }
};
modSpawning.maxCreeps = 20;

module.exports = modSpawning;
<commit_msg>removed disabling of merchants<commit_after>var roleArchitect = require('role.architect');
var bodyObj = require('module.bodyTypes');
var modCommon = require('module.common');

var modSpawning = {
  incrementNum:function(creepType){
    if(creepType==="harvester"){
      ++Memory.roles.numHarvesters;
    }else if(creepType === "upgrader"){
      ++Memory.roles.numUpgraders;
    }else if(creepType === "builder"){
      ++Memory.roles.numBuilders;
    }else if(creepType === "repair"){
      ++Memory.roles.numRepair;
    }else if (creepType === "architect"){
      ++Memory.roles.numArchitects;
    }else if(creepType === "miner"){
      ++Memory.roles.numMiners;
    }else if(creepType === "hauler"){
      ++Memory.roles.numHaulers;
    }else if(creepType === "feeder"){
      ++Memory.roles.numFeeders;
    }else if(creepType === "geo"){
      ++Memory.roles.numGeo;
    }else if(creepType === "geoHauler"){
      ++Memory.roles.numGeoH;
    }else if(creepType === "merchant"){
      ++Memory.roles.numMerchant;
    }
    ++Memory.roles.numCreeps;
  },

  spawn:function(spawner,energyCapacity,controllerLvl){
    //TODO put all of the constants here in memory after first run
    //Maybe an init method for the module
    var numSources = spawner.room.find(FIND_SOURCES).length;
    var numTowers = spawner.room.find(FIND_STRUCTURES, {filter: (struct)=> struct.structureType === STRUCTURE_TOWER}).length;
    var isStorage = (spawner.room.storage !== undefined) && (spawner.room.storage.store.energy > 1000);
    var betterHarvesters1 = Memory.roles.betterHarvesters1; // number of tier2 harvesters spawned, to track shift

    var betterHarvesters2 = Memory.roles.betterHarvesters2; // number of tier2 harvesters spawned, to track shift
    var controllerLvlMod = Math.ceil((controllerLvl-1)/3);  //modifier to the max amount of each creep

    var notEnoughHarvest = ((Memory.roles.numHarvesters < Memory.roles.maxHarvesters) && (Memory.roles.numMiners + Memory.roles.numHarvesters < 1));  //if there are enough harvesters, to stop other higher tier spawns

    var noHarvest = (Memory.roles.numHarvesters < 1) && (Memory.roles.numHaulers<1);

    var spawnTier1 = (energyCapacity<550 || (spawner.room.energyAvailable<550 && noHarvest)); //boolean for tier 1 spawning

    var spawnTier2 = (energyCapacity<800 || (spawner.room.energyAvailable<800 && notEnoughHarvest));  //boolean for tier2 spawning

    var spawnTier3 = (energyCapacity<1300 || (spawner.room.energyAvailable<800 && notEnoughHarvest));  //boolean for tier3 spawning

    var spawnTier4 = (energyCapacity>=1300);

    var flags = _.filter(spawner.room.find(FIND_FLAGS), (flag) => roleArchitect.ableToBuild(controllerLvl, flag.name));

    var body = [MOVE];

    var extractor = _.filter(spawner.room.find(FIND_STRUCTURES), (structure) => structure.structureType === STRUCTURE_EXTRACTOR);

    var market = _.filter(spawner.room.find(FIND_STRUCTURES), (structure) => structure.structureType === STRUCTURE_TERMINAL);

    var minerals = (_.filter(spawner.room.find(FIND_MINERALS), (mineral) => mineral.mineralAmount > 0).length) > 0;

    var toSell = !minerals && (modCommon.whatStore(spawner.room.storage) != RESOURCE_ENERGY);

    if(spawnTier1){
        //make sure there is energy and the spawner isnt already working, then spawn harvesters, upgrader, and repair in that priority
        if((!spawner.spawning) && (spawner.room.energyAvailable>=300)){
            if(Memory.roles.numHarvesters < (Memory.roles.maxHarvesters)){
              body = bodyObj.getBody('harvester',1);
              var hc = spawner.createCreep(body, undefined,{role: 'harvester',selfHarvest:true, source:""});
              console.log("Spawning Harvester: " + hc);
              this.incrementNum("harvester");
            }
            else if(Memory.roles.numUpgraders < Math.min(Memory.roles.maxUpgraders+controllerLvlMod, spawner.room.find(FIND_SOURCES).length)){
              body = bodyObj.getBody('upgrader',1);
              var uc = spawner.createCreep(body, undefined,{role: 'upgrader', selfHarvest:true});
              console.log("Spawning Upgrader: " + uc);
              this.incrementNum("upgrader");
            }
            else if(Memory.roles.numRepair< (Memory.roles.maxRepair - numTowers)){
              body = bodyObj.getBody('repair',1);
              var rc = spawner.createCreep(body, undefined,{role: 'repair', toFix:'', selfHarvest:true, military:true});
              console.log("Spawning Repair: " + rc);
              this.incrementNum("repair");
            }
            else if(flags.length && Memory.roles.numArchitects < 1){
              body = bodyObj.getBody('architect',1);
              var ac = spawner.createCreep(body, undefined,{role: 'architect', toFix:'', selfHarvest:false});
              console.log("Spawning Architect: " + ac);
              this.incrementNum("architect");
            }
        }
    }else if(spawnTier2){
      //make sure there is energy and the spawner isnt already working, then spawn harvesters, builders, upgrader, and repair in that priority
      var moveOn = (betterHarvesters1>=(Memory.roles.maxHarvesters));
      if((!spawner.spawning) && (spawner.room.energyAvailable>=550)){
          if(Memory.roles.numHarvesters < (Memory.roles.maxHarvesters)){
            body = bodyObj.getBody('harvester',2);
            var h2c = spawner.createCreep(body, undefined,{role: 'harvester',selfHarvest:true, source:""});
             console.log("Spawning Harvester: " + h2c);
             betterHarvesters1++;
             this.incrementNum("harvester");
          }
          else if(moveOn && spawner.room.find(FIND_MY_CONSTRUCTION_SITES).length && (Memory.roles.numBuilders < (Memory.roles.maxBuilders)) && Memory.roles.numArchitects === 0){
            body = bodyObj.getBody('builder',2);
            var b2c = spawner.createCreep(body, undefined,{role: 'builder',selfHarvest:false});
            console.log("Spawning Builder: " + b2c);
            this.incrementNum("builder");
          }
          else if(moveOn && Memory.roles.numUpgraders < Math.min(Memory.roles.maxUpgraders+controllerLvlMod, spawner.room.find(FIND_SOURCES).length)){
            body = bodyObj.getBody('upgrader',2);
            var u2c = spawner.createCreep(body, undefined,{role: 'upgrader', selfHarvest:false});
            console.log("Spawning Upgrader: " + u2c);
            this.incrementNum("upgrader");
          }
          else if(moveOn && Memory.roles.numRepair< (Memory.roles.maxRepair - numTowers)){
            body = bodyObj.getBody('repair',2);
            var r2c = spawner.createCreep(body, undefined,{role: 'repair', toFix:'', selfHarvest:true, military:true});
            console.log("Spawning Repair: " + r2c);
            this.incrementNum("repair");
          }
          else if(moveOn && flags.length && Memory.roles.numArchitects < 1){
            body = bodyObj.getBody('architect',2);
            var a2c = spawner.createCreep(body, undefined,{role: 'architect', toFix:'', selfHarvest:false});
            console.log("Spawning Architect: " + a2c);
            this.incrementNum("architect");
          }
      }
    }else if(spawnTier3){
      //make sure there is energy and the spawner isnt already working, then spawn harvesters, builders, upgrader, and repair in that priority
      var moveOn2 = (betterHarvesters2>=(Memory.roles.maxHarvesters));
      if((!spawner.spawning) && (spawner.room.energyAvailable>=800)){
        if(Memory.roles.numHarvesters < (Memory.roles.maxHarvesters)){
          body = bodyObj.getBody('harvester',3);
          var h3c = spawner.createCreep(body, undefined,{role: 'harvester',selfHarvest:true, source:""});
          console.log("Spawning Harvester: " + h3c);
          betterHarvesters2++;
          this.incrementNum("harvester");
        }
        else if(moveOn2 && spawner.room.find(FIND_MY_CONSTRUCTION_SITES).length && (Memory.roles.numBuilders < (Memory.roles.maxBuilders)) && Memory.roles.numArchitects === 0){
          body = bodyObj.getBody('builder',3);
          var b3c = spawner.createCreep(body, undefined,{role: 'builder',selfHarvest:false});
          console.log("Spawning Builder: " + b3c);
          this.incrementNum("builder");
        }
        else if(moveOn2 && Memory.roles.numUpgraders < Math.min(Memory.roles.maxUpgraders+controllerLvlMod, spawner.room.find(FIND_SOURCES).length)){
          body = bodyObj.getBody('upgrader',3);
          var u3c = spawner.createCreep(body, undefined,{role: 'upgrader', selfHarvest:false});
          console.log("Spawning Upgrader: " + u3c);
          this.incrementNum("upgrader");
        }
        else if(moveOn2 && Memory.roles.numRepair< (Memory.roles.maxRepair - numTowers)){
          body = bodyObj.getBody('repair',3);
          var r3c = spawner.createCreep(body, undefined,{role: 'repair', toFix:'', selfHarvest:true, military:true});
          console.log("Spawning Repair: " + r3c);
          this.incrementNum("repair");
        }
        else if(moveOn2 && flags.length && Memory.roles.numArchitects < 1){
          body = bodyObj.getBody('architect',3);
          var a3c = spawner.createCreep(body, undefined,{role: 'architect', toFix:'', selfHarvest:false});
          console.log("Spawning Architect: " + a3c);
          this.incrementNum("architect");
        }
      }
    }else if (spawnTier4){
      if(!spawner.spawning && (spawner.room.energyAvailable >=800)){
        var moveOn3 = (Memory.roles.numMiners >= numSources) && (Memory.roles.numHaulers>=1);
        if(Memory.roles.numMiners< numSources){
          body = bodyObj.getBody('miner',4);
          var mine = spawner.createCreep(body, undefined,{role: 'miner',source:"", dropOff: "", military:true});
          console.log("Spawning Miner: " + mine);
          this.incrementNum("miner");
        }
        else if(Memory.roles.numHaulers<Memory.roles.maxHaulers){
          body = bodyObj.getBody('hauler',4);
          var haul = spawner.createCreep(body, undefined,{role: 'hauler', military:true, dropOff:""});
          console.log("Spawning Hauler: " + haul);
          this.incrementNum("hauler");
        }
        else if(moveOn3 && Memory.roles.numFeeders<Memory.roles.maxFeeders && isStorage){
          body = bodyObj.getBody('feeder',4);
          var feed = spawner.createCreep(body, undefined,{role: 'feeder', military:true});
          console.log("Spawning Feeder: " + feed);
          this.incrementNum("feeder");
        }
        else if(moveOn3 && spawner.room.find(FIND_MY_CONSTRUCTION_SITES).length && (Memory.roles.numBuilders < (Memory.roles.maxBuilders)) && Memory.roles.numArchitects === 0){
          body = bodyObj.getBody('builder',4);
          var b4c = spawner.createCreep(body, undefined,{role: 'builder',selfHarvest:false});
          console.log("Spawning Builder: " + b4c);
          this.incrementNum("builder");
        }
        else if(moveOn3 && Memory.roles.numUpgraders < 1){
          body = bodyObj.getBody('upgrader',4);
          var u4c = spawner.createCreep(body, undefined,{role: 'upgrader', selfHarvest:false});
          console.log("Spawning Upgrader: " + u4c);
          this.incrementNum("upgrader");
        }
        else if(moveOn3 && Memory.roles.numRepair< (Memory.roles.maxRepair - numTowers)){
          body = bodyObj.getBody('repair',4);
          var r4c = spawner.createCreep(body, undefined,{role: 'repair', toFix:'', selfHarvest:false, military:true});
          console.log("Spawning Repair: " + r4c);
          this.incrementNum("repair");
        }
        else if(moveOn3 && flags.length && Memory.roles.numArchitects < 1){
          body = bodyObj.getBody('architect',4);
          var a4c = spawner.createCreep(body, undefined,{role: 'architect', toFix:'', selfHarvest:false});
          console.log("Spawning Architect: " + a4c);
          this.incrementNum("architect");
        }
        else if(moveOn3 && extractor && minerals && Memory.roles.numGeo < Memory.roles.maxGeo){
          body = bodyObj.getBody('geoMiner',4);
          var g4c = spawner.createCreep(body, undefined,{role: 'geo', mineral:"", dropOff:""});
          console.log("Spawning GeoMiner: " + g4c);
          this.incrementNum("geo");
        }
        else if(moveOn3 && extractor && Memory.roles.numGeoH < Memory.roles.maxGeoH){
          body = bodyObj.getBody('geoHauler',4);
          var gh4c = spawner.createCreep(body, undefined,{role: 'geoH', dropOff:""});
          console.log("Spawning GeoHauler: " + gh4c);
          this.incrementNum("geoHauler");
        }
        else if(moveOn3 && market && toSell && Memory.roles.numMerchant < Memory.roles.maxMerchant){
          body = bodyObj.getBody('merchant',4);
          var mer4c = spawner.createCreep(body, undefined,{role: 'merchant', terminal:"", storage:""});
          console.log("Spawning Merchant: " + mer4c);
          this.incrementNum("merchant");
        }
      }
    }
    Memory.roles.betterHarvesters1 = betterHarvesters1;
    Memory.roles.betterHarvesters2 = betterHarvesters2;

  }
};
modSpawning.maxCreeps = 20;

module.exports = modSpawning;
<|endoftext|>


<commit_before>var router = getRouter();
var socket = rootRequire('app/helpers/socket');
var io = socket('/users/application', ['admin', 'staff']);
var User = require('../model');
var Application = require('./model');
var Email = rootRequire('app/emails/model');
var extend = require('extend');

/**
* Create an application
* POST /users/application
* Auth
*/
router.post('/users/application', User.auth(), function (req, res) {
  User
    .findById(req.user._id)
    .select('email application role created')
    .exec(function (err, user) {
      if (err) return res.internalError();
      if (user.application) return res.singleError('You have already submitted an application');
      var errors = Application.validate(req.body);
      if (errors.length) return res.multiError(errors);
      extend(req.body, {
        status: Application.Status.PENDING,
        going: false,
        checked: false,
        created: Date.now(),
        door: false,
        resume: req.files.filedata[0].path
      });
      if (req.body.dietary) req.body.dietary = req.body.dietary.split('|');
      var application = new Application(req.body);
      application.save(function (err, application) {
        if (err) return res.internalError();
        user.application = application._id;
        user.save(function (err, user) {
          if (err) res.internalError();

          if (process.env.NODE_ENV == 'production') {
            var email = new Email({
              subject: 'Your Kent Hack Enough Application',
              body: '# Thanks for applying to Kent Hack Enough!\nWe appreciate your interest.',
              recipients: {
                emails: [user.email]
              }
            });
            email.send(false);
          }

          var response = {
            _id: user._id,
            email: user.email,
            role: user.role,
            created: user.created,
            application: application
          };
          io.emit('create', response);
          return res.json(response);
        });
      });
    });
});

/**
* Get the logged in user with their application
* GET /users/me/application
* Auth
*/
router.get('/users/me/application', User.auth(), function (req, res) {
  User
    .findById(req.user._id)
    .select('email application role created')
    .populate('application')
    .exec(function (err, user) {
      if (err) return res.internalError();
      return res.json(user);
    });
});

/**
* Get a list of users with their applications
* GET /users/application
* Auth -> admin, staff
*/
router.get('/users/application', User.auth('admin', 'staff'), function (req, res) {
  User
    .find()
    .select('email application role created')
    .populate('application')
    .exec(function (err, users) {
      if (err) return res.internalError();
      return res.json({users: users});
    });
});

/**
* Get a user by ID with their application
* GET /users/:id/application
* Auth -> admin, staff
*/
router.get('/users/:id/application', User.auth('admin', 'staff'), function (req, res) {
  User
    .findById(req.params.id)
    .select('email application role created')
    .populate('application')
    .exec(function (err, user) {
      if (err) return res.internalError();
      return res.json(user);
    });
});

/**
* Partially update the logged in user's application
* PATCH /users/me/application
* Auth
*/
router.patch('/users/me/application', User.auth(), function (req, res) {
  var errors = Application.validate(req.body);
  if (errors.length) return res.multiError(errors);
  if (req.body.dietary) req.body.dietary = req.body.dietary.split('|');
  User
    .findByIdAndUpdate(req.user._id)
    .select('email application role created')
    .exec(function (err, user) {
      if (err) return res.internalError();
      Application
        .findByIdAndUpdate(user.application, req.body)
        .exec(function (err, application) {
          var response = {
            _id: user._id,
            email: user.email,
            role: user.role,
            created: user.created,
            application: application
          };
          io.emit('update', response);
          return res.json(response);
        });
    });
});

/**
* Partially update a user's application by ID
* PATCH /users/:id/application
* Auth -> admin, staff
*/
router.patch('/users/:id/application', User.auth('admin', 'staff'), function (req, res) {
  if (req.body.dietary) req.body.dietary = req.body.dietary.split('|');
  User
    .findById(req.params.id)
    .select('email application role created')
    .exec(function (err, user) {
      if (err) return res.internalError();
      if (user.application) {
        Application
          .findByIdAndUpdate(user.application, req.body)
          .exec(function (err, application) {
            if (err) return res.internalError();
            var response = {
              _id: user._id,
              email: user.email,
              role: user.role,
              created: user.created,
              application: application
            };
            io.emit('update', response);
            return res.json(response);
          });
      } else {
        var application = new Application(req.body);
        application.created = Date.now();
        application.save(function (err, application) {
          user.application = application;
          user.save(function (err, user) {
            if (err) return res.internalError();
            var response = {
              _id: user._id,
              email: user.email,
              role: user.role,
              created: user.created,
              application: application
            };
            io.emit('update', response);
            return res.json(response);
          });
        });
      }
    });
});

/**
* Delete the logged in user's application
* DELETE /users/me/application
* Auth
*/
router.delete('/users/me/application', User.auth(), function (req, res) {
  User
    .findById(req.user._id)
    .select('application')
    .exec(function (err, user) {
      if (err) return res.internalError();
      Application
        .findById(user.application)
        .remove()
        .exec(function (err) {
          if (err) return res.internalError();
          var response = {_id: req.user._id};
          io.emit('delete', response);
          return res.json(response);
        });
    });
});

/**
* Delete a user's application by ID
* DELETE /users/:id/application
* Auth -> admin, staff
*/
router.delete('/users/:id/application', User.auth('admin', 'staff'), function (req, res) {
  User
    .findById(req.params.id)
    .select('application')
    .exec(function (err, user) {
      if (err) return res.internalError();
      Application
        .findById(user.application)
        .remove()
        .exec(function (err) {
          if (err) return res.internalError();
          var response = {_id: req.params.id};
          io.emit('delete', response);
          return res.json(response);
        });
    });
});<commit_msg>Fix error when not resume is not included<commit_after>var router = getRouter();
var socket = rootRequire('app/helpers/socket');
var io = socket('/users/application', ['admin', 'staff']);
var User = require('../model');
var Application = require('./model');
var Email = rootRequire('app/emails/model');
var extend = require('extend');

/**
* Create an application
* POST /users/application
* Auth
*/
router.post('/users/application', User.auth(), function (req, res) {
  User
    .findById(req.user._id)
    .select('email application role created')
    .exec(function (err, user) {
      if (err) return res.internalError();
      if (user.application) return res.singleError('You have already submitted an application');
      var errors = Application.validate(req.body);
      if (errors.length) return res.multiError(errors);
      extend(req.body, {
        status: Application.Status.PENDING,
        going: false,
        checked: false,
        created: Date.now(),
        door: false
      });
      if (req.files.filedata) {
        extend(req.body, {
          resume: req.files.filedata[0].path
        });
      }
      if (req.body.dietary) req.body.dietary = req.body.dietary.split('|');
      var application = new Application(req.body);
      application.save(function (err, application) {
        if (err) return res.internalError();
        user.application = application._id;
        user.save(function (err, user) {
          if (err) res.internalError();

          if (process.env.NODE_ENV == 'production') {
            var email = new Email({
              subject: 'Your Kent Hack Enough Application',
              body: '# Thanks for applying to Kent Hack Enough!\nWe appreciate your interest.',
              recipients: {
                emails: [user.email]
              }
            });
            email.send(false);
          }

          var response = {
            _id: user._id,
            email: user.email,
            role: user.role,
            created: user.created,
            application: application
          };
          io.emit('create', response);
          return res.json(response);
        });
      });
    });
});

/**
* Get the logged in user with their application
* GET /users/me/application
* Auth
*/
router.get('/users/me/application', User.auth(), function (req, res) {
  User
    .findById(req.user._id)
    .select('email application role created')
    .populate('application')
    .exec(function (err, user) {
      if (err) return res.internalError();
      return res.json(user);
    });
});

/**
* Get a list of users with their applications
* GET /users/application
* Auth -> admin, staff
*/
router.get('/users/application', User.auth('admin', 'staff'), function (req, res) {
  User
    .find()
    .select('email application role created')
    .populate('application')
    .exec(function (err, users) {
      if (err) return res.internalError();
      return res.json({users: users});
    });
});

/**
* Get a user by ID with their application
* GET /users/:id/application
* Auth -> admin, staff
*/
router.get('/users/:id/application', User.auth('admin', 'staff'), function (req, res) {
  User
    .findById(req.params.id)
    .select('email application role created')
    .populate('application')
    .exec(function (err, user) {
      if (err) return res.internalError();
      return res.json(user);
    });
});

/**
* Partially update the logged in user's application
* PATCH /users/me/application
* Auth
*/
router.patch('/users/me/application', User.auth(), function (req, res) {
  var errors = Application.validate(req.body);
  if (errors.length) return res.multiError(errors);
  if (req.body.dietary) req.body.dietary = req.body.dietary.split('|');
  User
    .findByIdAndUpdate(req.user._id)
    .select('email application role created')
    .exec(function (err, user) {
      if (err) return res.internalError();
      Application
        .findByIdAndUpdate(user.application, req.body)
        .exec(function (err, application) {
          var response = {
            _id: user._id,
            email: user.email,
            role: user.role,
            created: user.created,
            application: application
          };
          io.emit('update', response);
          return res.json(response);
        });
    });
});

/**
* Partially update a user's application by ID
* PATCH /users/:id/application
* Auth -> admin, staff
*/
router.patch('/users/:id/application', User.auth('admin', 'staff'), function (req, res) {
  if (req.body.dietary) req.body.dietary = req.body.dietary.split('|');
  User
    .findById(req.params.id)
    .select('email application role created')
    .exec(function (err, user) {
      if (err) return res.internalError();
      if (user.application) {
        Application
          .findByIdAndUpdate(user.application, req.body)
          .exec(function (err, application) {
            if (err) return res.internalError();
            var response = {
              _id: user._id,
              email: user.email,
              role: user.role,
              created: user.created,
              application: application
            };
            io.emit('update', response);
            return res.json(response);
          });
      } else {
        var application = new Application(req.body);
        application.created = Date.now();
        application.save(function (err, application) {
          user.application = application;
          user.save(function (err, user) {
            if (err) return res.internalError();
            var response = {
              _id: user._id,
              email: user.email,
              role: user.role,
              created: user.created,
              application: application
            };
            io.emit('update', response);
            return res.json(response);
          });
        });
      }
    });
});

/**
* Delete the logged in user's application
* DELETE /users/me/application
* Auth
*/
router.delete('/users/me/application', User.auth(), function (req, res) {
  User
    .findById(req.user._id)
    .select('application')
    .exec(function (err, user) {
      if (err) return res.internalError();
      Application
        .findById(user.application)
        .remove()
        .exec(function (err) {
          if (err) return res.internalError();
          var response = {_id: req.user._id};
          io.emit('delete', response);
          return res.json(response);
        });
    });
});

/**
* Delete a user's application by ID
* DELETE /users/:id/application
* Auth -> admin, staff
*/
router.delete('/users/:id/application', User.auth('admin', 'staff'), function (req, res) {
  User
    .findById(req.params.id)
    .select('application')
    .exec(function (err, user) {
      if (err) return res.internalError();
      Application
        .findById(user.application)
        .remove()
        .exec(function (err) {
          if (err) return res.internalError();
          var response = {_id: req.params.id};
          io.emit('delete', response);
          return res.json(response);
        });
    });
});<|endoftext|>


<commit_before>"use strict";
const path = require("path");

const looksLikeAbsolutePath = (maybeAbsolutePath) => {
	return /^(?:[a-z]:\\|\/)/i.test(maybeAbsolutePath);
};

const normalizePathSeparator = (p) => p.replace(/\\/g, "/");

const _makePathsRelative = (context, identifier) => {
	return identifier
		.split(/([|! ])/)
		.map(str => looksLikeAbsolutePath(str) ?
			normalizePathSeparator(path.relative(context, str)) : str)
		.join("");
};

exports.makePathsRelative = (context, identifier, cache) => {
	if(!cache) return _makePathsRelative(context, identifier);

	const relativePaths = cache.relativePaths || (cache.relativePaths = new Map());

	let cachedResult;
	let contextCache = relativePaths.get(context)
	if(typeof contextCache === "undefined") {
		relativePaths.set(context, contextCache = new Map());
	} else {
		cachedResult = contextCache.get(identifier)
	}

	if(typeof cachedResult !== "undefined") {
		return cachedResult;
	} else {
		const relativePath = _makePathsRelative(context, identifier);
		contextCache.set(identifier, relativePath);
		return relativePath;
	}
};
<commit_msg>added semis<commit_after>"use strict";
const path = require("path");

const looksLikeAbsolutePath = (maybeAbsolutePath) => {
	return /^(?:[a-z]:\\|\/)/i.test(maybeAbsolutePath);
};

const normalizePathSeparator = (p) => p.replace(/\\/g, "/");

const _makePathsRelative = (context, identifier) => {
	return identifier
		.split(/([|! ])/)
		.map(str => looksLikeAbsolutePath(str) ?
			normalizePathSeparator(path.relative(context, str)) : str)
		.join("");
};

exports.makePathsRelative = (context, identifier, cache) => {
	if(!cache) return _makePathsRelative(context, identifier);

	const relativePaths = cache.relativePaths || (cache.relativePaths = new Map());

	let cachedResult;
	let contextCache = relativePaths.get(context);
	if(typeof contextCache === "undefined") {
		relativePaths.set(context, contextCache = new Map());
	} else {
		cachedResult = contextCache.get(identifier);
	}

	if(typeof cachedResult !== "undefined") {
		return cachedResult;
	} else {
		const relativePath = _makePathsRelative(context, identifier);
		contextCache.set(identifier, relativePath);
		return relativePath;
	}
};
<|endoftext|>


<commit_before>(function(namespace) {

    var EVENTS = {
        MOVE: 'mousemove',
        SCROLL: 'scroll'
    },
    playDelay = 500,
    Player = function(events, $iframe){
        this.mouseEvents = [];
        this.scrollEvents = [];
        this.currentMouseEvent = 0;
        this.currentScrollEvent = 0;

        this.currentMouseEventTimestamp = events[0].timestamp - playDelay;
        this.mouseRealTimestamp = 0;
        this.mousePauseTimeout = 0;

        this.currentScrollEventTimestamp = events[0].timestamp - playDelay;
        this.scrollRealTimestamp = 0;
        this.scrollPauseTimeout = 0;

        this.$iframe = $iframe;
        this.$mouse = null;

        this.groupEvents(events);
        this.createMouse();
    };
    Player.prototype = {
        groupEvents: function(events) {
            for(var i in events) {
                events[i].type = events[i].type.toLowerCase();
                if(events[i].type == EVENTS.SCROLL) {
                    this.scrollEvents.push(events[i]);
                }
                else {
                    this.mouseEvents.push(events[i]);
                }
            }
        },
        createMouse: function() {
            if(!this.$mouse) {
                this.$iframe.append('
Mouse
');
                this.$mouse = this.$iframe.find('#mouse');
            }
        },
        play: function() {
            this.paused = false;
            this.playScroll();
            this.playMouse();
        },
        pause: function() {
            this.paused = true;
            clearTimeout(this.scrollTimeout);
            clearTimeout(this.mouseTimeout);
            this.$mouse.stop();
            this.mousePauseTimeout += (new Date()).getTime() - this.mouseRealTimestamp;
            this.scrollPauseTimeout += (new Date()).getTime() - this.scrollRealTimestamp;
        },
        playMouse: function() {
            if(!this.paused && this.mouseEvents[this.currentMouseEvent]) {
                var self = this;
                if(this.currentMouseEvent === 0) {
                    var event = this.mouseEvents[this.currentMouseEvent],
                        timeout = event.timestamp - this.currentMouseEventTimestamp;
                    this.mouseRealTimestamp = (new Date()).getTime();
                    this.mouseTimeout = setTimeout(function() {
                        if(!self.paused) {
                            self.currentMouseEvent++;
                            self.currentMouseEventTimestamp = event.timestamp;
                            self.showEvent(event);
                            self.playMouse();
                        }
                    }, timeout);
                }
                else {
                    var currentEvent = this.mouseEvents[this.currentMouseEvent],
                        previousEvent = this.mouseEvents[this.currentMouseEvent - 1],
                        tmpPauseTimestamp = this.mousePauseTimeout;
                    this.mouseRealTimestamp = (new Date()).getTime();
                    this.$mouse.animate({
                        top: currentEvent.y,
                        left: currentEvent.x
                    }, currentEvent.timestamp - previousEvent.timestamp - tmpPauseTimestamp, function() {
                        if(!self.paused) {
                            self.mousePauseTimeout = 0;
                            self.currentMouseEvent++;
                            self.currentMouseEventTimestamp = currentEvent.timestamp;
                            self.showEvent(currentEvent);
                            self.playMouse();
                        }
                    });
                }
            }
        },
        playScroll: function() {
            if(!this.paused && this.scrollEvents[this.currentScrollEvent]) {
                var self = this,
                    event = this.scrollEvents[this.currentScrollEvent],
                    timeout = event.timestamp - this.currentScrollEventTimestamp - this.scrollPauseTimeout;
                this.scrollRealTimestamp = (new Date()).getTime();
                this.scrollTimeout = setTimeout(function() {
                    if(!self.paused) {
                        self.scrollPauseTimeout = 0;
                        self.currentScrollEvent++;
                        self.currentScrollEventTimestamp = event.timestamp;
                        self.showEvent(event);
                        self.playScroll();
                    }
                }, timeout);
            }
        },
        showEvent: function(event) {
            console.log(event);
            switch(event.type) {
                case EVENTS.SCROLL:
                    this.$iframe.scrollTop(event.y);
                    this.$iframe.scrollLeft(event.x);
                    break;
                default:
                    this.$iframe.append('
' + this.currentMouseEvent + '-' + event.type + '
');
                    break;
            }
        }
    };

    namespace.Player = Player;
})(window);

(function(namespace) {

    var EVENTS = 'data-events',
        WIDTH = 'data-window-width',
        HEIGHT = 'data-window-height',
        URL = 'data-url',
    Pageviews = function(events) {
        this.$events = $(events);
        this.$iframe = this.getIframe();
        this.events = this.parseEvents();
        this.init();

    };
    Pageviews.prototype = {
        parseEvents: function() {
            return $.parseJSON(this.$events.attr(EVENTS));
        },
        getIframe: function() {
            var $iframe = $('#pageview_window');
            $iframe.attr('src', this.$events.attr(URL));
            $iframe.attr('width', this.$events.attr(WIDTH));
            $iframe.attr('height', this.$events.attr(HEIGHT));
            return $iframe;
        },
        init: function() {
            var player;
            this.$iframe.on('load', $.proxy(function() {
                this.player = player = new window.Player(this.events, this.$iframe.contents().find('body'));
            }, this));
            $('button.play').on('click', function() {
                if(player) {
                    var $el = $(this);
                    if($el.hasClass('play')) {
                        $el.toggleClass('play').text('pause');
                        player.play()
                    }
                    else {
                        $el.toggleClass('play').text('play');
                        player.pause();
                    }
                }
            });
        }
    };

    namespace.Pageviews = Pageviews;
})(window);

$(document).on('ready', function() {
    if($('body').hasClass('pageviews')) {
        var pageviews = new window.Pageviews('#events');
    }
});<commit_msg>added proper namespace<commit_after>mouseRecorder = mouseRecorder || {};
mouseRecorder.modules = mouseRecorder.modules || {};
mouseRecorder.views = mouseRecorder.views || {};

(function(namespace) {

    var EVENTS = {
        MOVE: 'mousemove',
        SCROLL: 'scroll'
    },
    playDelay = 500,
    Player = function(events, $iframe){
        this.mouseEvents = [];
        this.scrollEvents = [];
        this.currentMouseEvent = 0;
        this.currentScrollEvent = 0;

        this.currentMouseEventTimestamp = events[0].timestamp - playDelay;
        this.mouseRealTimestamp = 0;
        this.mousePauseTimeout = 0;

        this.currentScrollEventTimestamp = events[0].timestamp - playDelay;
        this.scrollRealTimestamp = 0;
        this.scrollPauseTimeout = 0;

        this.$iframe = $iframe;
        this.$mouse = null;

        this.groupEvents(events);
        this.createMouse();
    };
    Player.prototype = {
        groupEvents: function(events) {
            for(var i in events) {
                events[i].type = events[i].type.toLowerCase();
                if(events[i].type == EVENTS.SCROLL) {
                    this.scrollEvents.push(events[i]);
                }
                else {
                    this.mouseEvents.push(events[i]);
                }
            }
        },
        createMouse: function() {
            if(!this.$mouse) {
                this.$iframe.append('
Mouse
');
                this.$mouse = this.$iframe.find('#mouse');
            }
        },
        play: function() {
            this.paused = false;
            this.playScroll();
            this.playMouse();
        },
        pause: function() {
            this.paused = true;
            clearTimeout(this.scrollTimeout);
            clearTimeout(this.mouseTimeout);
            this.$mouse.stop();
            this.mousePauseTimeout += (new Date()).getTime() - this.mouseRealTimestamp;
            this.scrollPauseTimeout += (new Date()).getTime() - this.scrollRealTimestamp;
        },
        playMouse: function() {
            if(!this.paused && this.mouseEvents[this.currentMouseEvent]) {
                var self = this;
                if(this.currentMouseEvent === 0) {
                    var event = this.mouseEvents[this.currentMouseEvent],
                        timeout = event.timestamp - this.currentMouseEventTimestamp;
                    this.mouseRealTimestamp = (new Date()).getTime();
                    this.mouseTimeout = setTimeout(function() {
                        if(!self.paused) {
                            self.currentMouseEvent++;
                            self.currentMouseEventTimestamp = event.timestamp;
                            self.showEvent(event);
                            self.playMouse();
                        }
                    }, timeout);
                }
                else {
                    var currentEvent = this.mouseEvents[this.currentMouseEvent],
                        previousEvent = this.mouseEvents[this.currentMouseEvent - 1],
                        tmpPauseTimestamp = this.mousePauseTimeout;
                    this.mouseRealTimestamp = (new Date()).getTime();
                    this.$mouse.animate({
                        top: currentEvent.y,
                        left: currentEvent.x
                    }, currentEvent.timestamp - previousEvent.timestamp - tmpPauseTimestamp, function() {
                        if(!self.paused) {
                            self.mousePauseTimeout = 0;
                            self.currentMouseEvent++;
                            self.currentMouseEventTimestamp = currentEvent.timestamp;
                            self.showEvent(currentEvent);
                            self.playMouse();
                        }
                    });
                }
            }
        },
        playScroll: function() {
            if(!this.paused && this.scrollEvents[this.currentScrollEvent]) {
                var self = this,
                    event = this.scrollEvents[this.currentScrollEvent],
                    timeout = event.timestamp - this.currentScrollEventTimestamp - this.scrollPauseTimeout;
                this.scrollRealTimestamp = (new Date()).getTime();
                this.scrollTimeout = setTimeout(function() {
                    if(!self.paused) {
                        self.scrollPauseTimeout = 0;
                        self.currentScrollEvent++;
                        self.currentScrollEventTimestamp = event.timestamp;
                        self.showEvent(event);
                        self.playScroll();
                    }
                }, timeout);
            }
        },
        showEvent: function(event) {
            switch(event.type) {
                case EVENTS.SCROLL:
                    this.$iframe.scrollTop(event.y);
                    this.$iframe.scrollLeft(event.x);
                    break;
                default:
                    this.$iframe.append('
' + this.currentMouseEvent + '-' + event.type + '
');
                    break;
            }
        }
    };

    namespace.Player = Player;
})(mouseRecorder.modules);

(function(namespace) {

    var EVENTS = 'data-events',
        WIDTH = 'data-window-width',
        HEIGHT = 'data-window-height',
        URL = 'data-url',
    Pageviews = function(events) {
        this.$events = $(events);
        this.$iframe = this.getIframe();
        this.events = this.parseEvents();
        this.init();

    };
    Pageviews.prototype = {
        parseEvents: function() {
            return $.parseJSON(this.$events.attr(EVENTS));
        },
        getIframe: function() {
            var $iframe = $('#pageview_window');
            $iframe.attr('src', this.$events.attr(URL));
            $iframe.attr('width', this.$events.attr(WIDTH));
            $iframe.attr('height', this.$events.attr(HEIGHT));
            return $iframe;
        },
        init: function() {
            var player;
            this.$iframe.on('load', $.proxy(function() {
                this.player = player = new mouseRecorder.modules.Player(this.events, this.$iframe.contents().find('body'));
            }, this));
            $('button.play').on('click', function() {
                if(player) {
                    var $el = $(this);
                    if($el.hasClass('play')) {
                        $el.toggleClass('play').text('pause');
                        player.play()
                    }
                    else {
                        $el.toggleClass('play').text('play');
                        player.pause();
                    }
                }
            });
        }
    };

    namespace.Pageviews = Pageviews;
})(mouseRecorder.views);

$(document).on('ready', function() {
    if($('body').hasClass('pageviews')) {
        var pageviews = new mouseRecorder.views.Pageviews('#events');
    }
});<|endoftext|>


<commit_before>var crypto=require('crypto');
var formidable=require('formidable');
var DB;
const lifeTime=2592000;//有效時間(秒)，超時後從資料庫刪除，預設30天
const checkLifeTime=86400000;//清理間隔時間(毫秒)，檢查超時間隔時間，預設1天
const cacheTime=3600000;//快取有效時間(毫秒)，超時後寫出記憶體，預設1小時
const dbLogTime=600000;//寫入資料庫間隔時間(毫秒)，預設10分鐘
const formLimit={
	'maxFields': 3,
	'maxFieldsSize': 512
};

var maps={};

function Map(){
	var nowTime=new Date().getTime();
	this.id=null;//地圖id，null=未確認
	this.points={};//標記點
	this.links=[];//在線列表
	this.lastLogTime=nowTime;//最後記錄時間
	this.lastUpdateTime=nowTime;//最後更動時間
	this.lastReadTime=nowTime;//最後讀取時間
	this.updatedPoints=[];//上次紀錄後更動了哪些點
	this.deletedPoints=[];//上次紀錄後刪除了哪些點
}
Map.prototype.sendAll=function(msg){
	msg=JSON.stringify(msg);
	this.links.forEach(function(link){
		link.send(msg);
	});
}
Map.prototype.join=function(link){
	this.lastReadTime=new Date().getTime();
	this.links.push(link);
	var points=this.points;
	var point;
	for(var hashName in points){
		point=points[hashName];
		link.send(JSON.stringify({
			'name': point.name,
			'pos': point.pos,
			'module': point.module,
			'args': point.args
		}));
	}
	points=undefined;
	delete point,points;
	var _=this;
	link.on('message',function(data){
		if(data.type!='utf8') return;
		var msg=JSON.parse(data.utf8Data);
		switch(msg.action){
			case 'move':
				if(Number.isNaN(msg.pos[0]) || Number.isNaN(msg.pos[1]) || !Array.isArray(msg.args))
					return;
				_.movePoint(msg.name,msg.pos,msg.module,msg.args);
			break;
			case 'delete':
				_.deletePoint(msg.name);
			break;
		}
	});
	link.on('close',function(){
		_.lastReadTime=new Date().getTime();
		_.links.splice(_.links.indexOf(link),1);
		_=link=undefined;
		delete _,link;
	});
}
Map.prototype.movePoint=function(name,pos,module,args){
	var hashName=md5(name);
	var point
	if(!(point=this.points[hashName])){
		point=this.points[hashName]={'name':name};
	}
	if(pos!=undefined) point.pos=pos;
	if(module!=undefined) point.module=module;
	if(args!=undefined) point.args=args;
	this.lastUpdateTime=new Date().getTime();
	if(this.updatedPoints.indexOf(hashName)<0)
		this.updatedPoints.push(hashName);
	this.sendAll({
		'action': 'move',
		'name': name,
		'pos': pos,
		'module': module,
		'args': args
	});
}
Map.prototype.deletePoint=function(name){
	var hashName=md5(name);
	if(!this.points[hashName]) return;
	var pointId=this.points[hashName].id;
	if(pointId){
		if(this.deletedPoints.indexOf(pointId)<0)
			this.deletedPoints.push(pointId);
		var checkUpdated;
		if(checkUpdated=this.updatedPoints.indexOf(name)>=0)
			this.updatedPoints.splice(checkUpdated,1);
	}
	this.points[hashName]=undefined;
	delete this.points[hashName];
	this.lastUpdateTime=new Date().getTime();
	this.sendAll({
		'action': 'delete',
		'name': name
	});
}

function md5(data){
	return crypto.createHash('md5').update(data).digest('hex');
}
function getMap(name){
	var hashName=md5(name);
	var map;
	if(map=maps[hashName])
		return map;
	map=maps[hashName]=new Map();
	DB.query(
		'SELECT `points`.* \
		FROM `maps`,`points` \
		WHERE `maps`.`name`=? AND `maps`.`id`=`points`.`mapId`',
		[hashName],
		function(err,points){
			if(!points.length){
				map.id=false;
				return;
			}
			map.id=points[0].mapId;
			points.forEach(function(point){
				var name=point.name;
				if(this.points[name] || this.deletedPoints.indexOf(name)>=0)
					return;
				this.points[name]={
					'id': point.id,
					'pos': [point.posX,point.posY],
					'module': point.module,
					'args': JSON.parse(point.args)
				};
			},map);
		}
	);
	return map;
}
var writeDB=(function(){
	var createMap=function(hashName,callback){
		var map=maps[hashName];
		DB.query('INSERT INTO `maps` (`name`,`lastReadTime`) VALUE (?)',[hashName,map.lastReadTime],function(err,result){
			if(err){
				console.log('[mapPoint] 無法建立地圖 hashName=%s',hashName);
				return;
			}
			map.id=result.insertId;
			callback(hashName);
		});
	};
	var writeMap=function(hashName){
		var map=maps[hashName];
		var mapId=map.id;
		DB.query(
			'UPDATE `maps` SET `lastReadTime`=? WHERE `id`=?',
			[map.lastReadTime,mapId],
			function(err){
				if(err){
					console.log('[mapPoint] 地圖最後讀取時間更新失敗 id=%d',mapId);
				}
			}
		);
		map.updatedPoints.forEach(function(key,index,o){
			var point=this[key];
			if(point.id)
				DB.query(
					'UPDATE `points` SET `posX`=?, `posY`=?, `module`=?,`args`=? WHERE `id`=?',
					[point.pos[0],point.pos[1],point.module,JSON.stringify(point.args),point.id],
					function(err){
						if(err){
							console.log('[mapPoint] 更新標記點失敗 id=%d',point.id);
						}
					}
				);
			else
				DB.query(
					'INSERT INTO `points` (`mapId`,`posX`,`posY`,`module`,`args`) VALUE (?)',
					[mapId,point.pos[0],point.pos[1],point.module,JSON.stringify(point.args),point.id],
					function(err,result){
						if(err){
							console.log('[mapPoint] 新增標記點失敗 map=%s,name=%s',hashName,point.name);
							return;
						}
						point.id=result.insertId;
					}
				);
		},map.points);
		if(map.deletedPoints.length){
			var list=map.deletedPoints;
			DB.query('DELETE FROM `point` WHERE `id` IN (?)',list,function(err){
				if(err){
					console.log('[mapPoint] 刪除標記點失敗 id=%s',list.join(','));
				}
				list=undefined;
				delete list;
			});
			map.deletedPoints=[];
		}

	};
	return function(){
		var outTime=new Date().getTime()-cacheTime;
		var hashName,map;
		for(hashName in maps){
			map=maps[hashName];
			if(map.updatedPoints.length || map.deletedPoints.length){
				if(map.id)
					writeMap(hashName);
				else
					createMap(hashName,writeMap);
			}
			if(map.link.length && map.lastReadTime<outTime){
				maps[hashName]=undefined;
				delete maps[hashName];
			}
		}
		map=undefined;
		delete map,hashName;
	};
})();

//定時分散寫入
setInterval(writeDB,dbLogTime);
//定時清理資料庫
setInterval(function(){
	var expTime=Math.floor(new Date().getTime()/1000-lifeTime);
	DB.query('SELECT `id`,`name` FROM `maps` WHERE `lastReadTime`<?',[expTime],function(err,deleteList){
		deleteList=deleteList.reduce(function(r,v){
			if(maps[v.name]) return r;
			r.push(v.id);
			return r;
		},[]);
		DB.query('DELETE FROM `maps` WHERE `id` IN (?)',[deleteList],function(err){
			if(err){
				console.log('[mapPoint] 清理地圖資料失敗，目標清單 id=%s，錯誤訊息：%s',deleteList.join(','),err);
			}
		});
		DB.query('DELETE FROM `points` WHERE `mapId` IN (?)',[deleteList],function(err){
			if(err){
				console.log('[mapPoint] 清理標記點資料失敗，目標清單 id=%s，錯誤訊息：%s',deleteList.join(','),err);
			}
		});
	});
},checkLifeTime);
//退出前寫出記憶體
process.on('exit',function(){
	for(var hashName in maps){
		maps[hashName].links.forEach(function(link){
			link.close();
		});
	}
	writeDB();
});

module.exports={
	'load': function(control){
		DB=control.connectDB();
		var loaded=(function(control){
			var step=2;
			return function(){
				if(--step) return;
				delete step;
				loaded=undefined;
				delete loaded;
				control.onLoad('mapPoint');
			}
		})(control);
		DB.query(
			'CREATE TABLE IF NOT EXISTS `maps` (\
				`id` int(10) unsigned NOT NULL AUTO_INCREMENT,\
				`name` char(32) NOT NULL,\
				`lastReadTime` int(10) unsigned NOT NULL,\
				PRIMARY KEY (`id`),\
				KEY `mapId` (`name`)\
			) ENGINE=MyISAM  DEFAULT CHARSET=latin1 COMMENT=\'地圖\' AUTO_INCREMENT=1;',
			function(err){
				if(!err){
					loaded();
					return;
				}
				console.error('[mapPoint] 表 maps 檢查或建立失敗');
				process.exit();
			}
		);
		DB.query(
			'CREATE TABLE IF NOT EXISTS `points` (\
				`id` int(10) unsigned NOT NULL AUTO_INCREMENT,\
				`mapId` int(10) unsigned NOT NULL,\
				`name` varchar(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,\
				`posX` double(10,7) NOT NULL,\
				`posY` double(10,7) NOT NULL,\
				`module` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,\
				`args` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,\
				PRIMARY KEY (`id`),\
				KEY `mapId` (`mapId`)\
			) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT=\'標記點\' AUTO_INCREMENT=1;',
			function(err){
				if(!err){
					loaded();
					return;
				}
				console.error('[mapPoint] 表 points 檢查或建立失敗');
				process.exit();
			}
		);
		control.mountHttp('post','/:name(\\w+)',function(req,res){
			var name=req.params.name;
			var map=getMap(name);
			var form=new formidable.IncomingForm(formLimit);
			form.on('error',function(e){
				req.connection.destroy();
			});
			form.parse(req, function(err,fields){
				switch(fields.action){
					case 'move':
						var pos=fields.pos.split(',');
						pos[0]=parseFloat(pos[0],10);
						pos[1]=parseFloat(pos[1],10);
						var args=JSON.parse(fields.args);
						if(Number.isNaN(pos[0]) || Number.isNaN(pos[1]) || !Array.isArray(args)){
							res.writeHead(400);
							res.end();
							return;
						}
						map.movePoint(fields.name,pos,fields.module,args);
					break;
					case 'delete':
						map.deletePoint(fields.name);
					break;
					default:
						res.writeHead(404);
						res.end();
						return;
				}
				res.writeHead(200);
				res.end();
			});
		});
		control.mountWebSocket('mapPoint',/^\/\w+$/,function(url,link){
			var name=url.pathname.substring(1,url.pathname.length);
			getMap(name).join(link);
		});
	}
};<commit_msg>fix: 新連線發送資料時未指定操作類型<commit_after>var crypto=require('crypto');
var formidable=require('formidable');
var DB;
const lifeTime=2592000;//有效時間(秒)，超時後從資料庫刪除，預設30天
const checkLifeTime=86400000;//清理間隔時間(毫秒)，檢查超時間隔時間，預設1天
const cacheTime=3600000;//快取有效時間(毫秒)，超時後寫出記憶體，預設1小時
const dbLogTime=600000;//寫入資料庫間隔時間(毫秒)，預設10分鐘
const formLimit={
	'maxFields': 3,
	'maxFieldsSize': 512
};

var maps={};

function Map(){
	var nowTime=new Date().getTime();
	this.id=null;//地圖id，null=未確認
	this.points={};//標記點
	this.links=[];//在線列表
	this.lastLogTime=nowTime;//最後記錄時間
	this.lastUpdateTime=nowTime;//最後更動時間
	this.lastReadTime=nowTime;//最後讀取時間
	this.updatedPoints=[];//上次紀錄後更動了哪些點
	this.deletedPoints=[];//上次紀錄後刪除了哪些點
}
Map.prototype.sendAll=function(msg){
	msg=JSON.stringify(msg);
	this.links.forEach(function(link){
		link.send(msg);
	});
}
Map.prototype.join=function(link){
	this.lastReadTime=new Date().getTime();
	this.links.push(link);
	var points=this.points;
	var point;
	for(var hashName in points){
		point=points[hashName];
		link.send(JSON.stringify({
			'action': 'move',
			'name': point.name,
			'pos': point.pos,
			'module': point.module,
			'args': point.args
		}));
	}
	points=undefined;
	delete point,points;
	var _=this;
	link.on('message',function(data){
		if(data.type!='utf8') return;
		var msg=JSON.parse(data.utf8Data);
		switch(msg.action){
			case 'move':
				if(Number.isNaN(msg.pos[0]) || Number.isNaN(msg.pos[1]) || !Array.isArray(msg.args))
					return;
				_.movePoint(msg.name,msg.pos,msg.module,msg.args);
			break;
			case 'delete':
				_.deletePoint(msg.name);
			break;
		}
	});
	link.on('close',function(){
		_.lastReadTime=new Date().getTime();
		_.links.splice(_.links.indexOf(link),1);
		_=link=undefined;
		delete _,link;
	});
}
Map.prototype.movePoint=function(name,pos,module,args){
	var hashName=md5(name);
	var point
	if(!(point=this.points[hashName])){
		point=this.points[hashName]={'name':name};
	}
	if(pos!=undefined) point.pos=pos;
	if(module!=undefined) point.module=module;
	if(args!=undefined) point.args=args;
	this.lastUpdateTime=new Date().getTime();
	if(this.updatedPoints.indexOf(hashName)<0)
		this.updatedPoints.push(hashName);
	this.sendAll({
		'action': 'move',
		'name': name,
		'pos': pos,
		'module': module,
		'args': args
	});
}
Map.prototype.deletePoint=function(name){
	var hashName=md5(name);
	if(!this.points[hashName]) return;
	var pointId=this.points[hashName].id;
	if(pointId){
		if(this.deletedPoints.indexOf(pointId)<0)
			this.deletedPoints.push(pointId);
		var checkUpdated;
		if(checkUpdated=this.updatedPoints.indexOf(name)>=0)
			this.updatedPoints.splice(checkUpdated,1);
	}
	this.points[hashName]=undefined;
	delete this.points[hashName];
	this.lastUpdateTime=new Date().getTime();
	this.sendAll({
		'action': 'delete',
		'name': name
	});
}

function md5(data){
	return crypto.createHash('md5').update(data).digest('hex');
}
function getMap(name){
	var hashName=md5(name);
	var map;
	if(map=maps[hashName])
		return map;
	map=maps[hashName]=new Map();
	DB.query(
		'SELECT `points`.* \
		FROM `maps`,`points` \
		WHERE `maps`.`name`=? AND `maps`.`id`=`points`.`mapId`',
		[hashName],
		function(err,points){
			if(!points.length){
				map.id=false;
				return;
			}
			map.id=points[0].mapId;
			points.forEach(function(point){
				var name=point.name;
				if(this.points[name] || this.deletedPoints.indexOf(name)>=0)
					return;
				this.points[name]={
					'id': point.id,
					'pos': [point.posX,point.posY],
					'module': point.module,
					'args': JSON.parse(point.args)
				};
			},map);
		}
	);
	return map;
}
var writeDB=(function(){
	var createMap=function(hashName,callback){
		var map=maps[hashName];
		DB.query('INSERT INTO `maps` (`name`,`lastReadTime`) VALUE (?)',[hashName,map.lastReadTime],function(err,result){
			if(err){
				console.log('[mapPoint] 無法建立地圖 hashName=%s',hashName);
				return;
			}
			map.id=result.insertId;
			callback(hashName);
		});
	};
	var writeMap=function(hashName){
		var map=maps[hashName];
		var mapId=map.id;
		DB.query(
			'UPDATE `maps` SET `lastReadTime`=? WHERE `id`=?',
			[map.lastReadTime,mapId],
			function(err){
				if(err){
					console.log('[mapPoint] 地圖最後讀取時間更新失敗 id=%d',mapId);
				}
			}
		);
		map.updatedPoints.forEach(function(key,index,o){
			var point=this[key];
			if(point.id)
				DB.query(
					'UPDATE `points` SET `posX`=?, `posY`=?, `module`=?,`args`=? WHERE `id`=?',
					[point.pos[0],point.pos[1],point.module,JSON.stringify(point.args),point.id],
					function(err){
						if(err){
							console.log('[mapPoint] 更新標記點失敗 id=%d',point.id);
						}
					}
				);
			else
				DB.query(
					'INSERT INTO `points` (`mapId`,`posX`,`posY`,`module`,`args`) VALUE (?)',
					[mapId,point.pos[0],point.pos[1],point.module,JSON.stringify(point.args),point.id],
					function(err,result){
						if(err){
							console.log('[mapPoint] 新增標記點失敗 map=%s,name=%s',hashName,point.name);
							return;
						}
						point.id=result.insertId;
					}
				);
		},map.points);
		if(map.deletedPoints.length){
			var list=map.deletedPoints;
			DB.query('DELETE FROM `point` WHERE `id` IN (?)',list,function(err){
				if(err){
					console.log('[mapPoint] 刪除標記點失敗 id=%s',list.join(','));
				}
				list=undefined;
				delete list;
			});
			map.deletedPoints=[];
		}

	};
	return function(){
		var outTime=new Date().getTime()-cacheTime;
		var hashName,map;
		for(hashName in maps){
			map=maps[hashName];
			if(map.updatedPoints.length || map.deletedPoints.length){
				if(map.id)
					writeMap(hashName);
				else
					createMap(hashName,writeMap);
			}
			if(map.link.length && map.lastReadTime<outTime){
				maps[hashName]=undefined;
				delete maps[hashName];
			}
		}
		map=undefined;
		delete map,hashName;
	};
})();

//定時分散寫入
setInterval(writeDB,dbLogTime);
//定時清理資料庫
setInterval(function(){
	var expTime=Math.floor(new Date().getTime()/1000-lifeTime);
	DB.query('SELECT `id`,`name` FROM `maps` WHERE `lastReadTime`<?',[expTime],function(err,deleteList){
		deleteList=deleteList.reduce(function(r,v){
			if(maps[v.name]) return r;
			r.push(v.id);
			return r;
		},[]);
		DB.query('DELETE FROM `maps` WHERE `id` IN (?)',[deleteList],function(err){
			if(err){
				console.log('[mapPoint] 清理地圖資料失敗，目標清單 id=%s，錯誤訊息：%s',deleteList.join(','),err);
			}
		});
		DB.query('DELETE FROM `points` WHERE `mapId` IN (?)',[deleteList],function(err){
			if(err){
				console.log('[mapPoint] 清理標記點資料失敗，目標清單 id=%s，錯誤訊息：%s',deleteList.join(','),err);
			}
		});
	});
},checkLifeTime);
//退出前寫出記憶體
process.on('exit',function(){
	for(var hashName in maps){
		maps[hashName].links.forEach(function(link){
			link.close();
		});
	}
	writeDB();
});

module.exports={
	'load': function(control){
		DB=control.connectDB();
		var loaded=(function(control){
			var step=2;
			return function(){
				if(--step) return;
				delete step;
				loaded=undefined;
				delete loaded;
				control.onLoad('mapPoint');
			}
		})(control);
		DB.query(
			'CREATE TABLE IF NOT EXISTS `maps` (\
				`id` int(10) unsigned NOT NULL AUTO_INCREMENT,\
				`name` char(32) NOT NULL,\
				`lastReadTime` int(10) unsigned NOT NULL,\
				PRIMARY KEY (`id`),\
				KEY `mapId` (`name`)\
			) ENGINE=MyISAM  DEFAULT CHARSET=latin1 COMMENT=\'地圖\' AUTO_INCREMENT=1;',
			function(err){
				if(!err){
					loaded();
					return;
				}
				console.error('[mapPoint] 表 maps 檢查或建立失敗');
				process.exit();
			}
		);
		DB.query(
			'CREATE TABLE IF NOT EXISTS `points` (\
				`id` int(10) unsigned NOT NULL AUTO_INCREMENT,\
				`mapId` int(10) unsigned NOT NULL,\
				`name` varchar(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,\
				`posX` double(10,7) NOT NULL,\
				`posY` double(10,7) NOT NULL,\
				`module` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,\
				`args` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,\
				PRIMARY KEY (`id`),\
				KEY `mapId` (`mapId`)\
			) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT=\'標記點\' AUTO_INCREMENT=1;',
			function(err){
				if(!err){
					loaded();
					return;
				}
				console.error('[mapPoint] 表 points 檢查或建立失敗');
				process.exit();
			}
		);
		control.mountHttp('post','/:name(\\w+)',function(req,res){
			var name=req.params.name;
			var map=getMap(name);
			var form=new formidable.IncomingForm(formLimit);
			form.on('error',function(e){
				req.connection.destroy();
			});
			form.parse(req, function(err,fields){
				switch(fields.action){
					case 'move':
						var pos=fields.pos.split(',');
						pos[0]=parseFloat(pos[0],10);
						pos[1]=parseFloat(pos[1],10);
						var args=JSON.parse(fields.args);
						if(Number.isNaN(pos[0]) || Number.isNaN(pos[1]) || !Array.isArray(args)){
							res.writeHead(400);
							res.end();
							return;
						}
						map.movePoint(fields.name,pos,fields.module,args);
					break;
					case 'delete':
						map.deletePoint(fields.name);
					break;
					default:
						res.writeHead(404);
						res.end();
						return;
				}
				res.writeHead(200);
				res.end();
			});
		});
		control.mountWebSocket('mapPoint',/^\/\w+$/,function(url,link){
			var name=url.pathname.substring(1,url.pathname.length);
			getMap(name).join(link);
		});
	}
};<|endoftext|>


<commit_before>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2012-2013 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

'use strict';

YUI.add('environment-change-set', function(Y) {
  var ns = Y.namespace('juju');

  var name = 'environment-change-set';

  ns.EnvironmentChangeSet = Y.Base.create(name, Y.Base, [], {

    /**
      Creates the persistent data storage object.

      @method initializer
    */
    initializer: function() {
      this.changeSet = {};
    },

    /* ECS methods */

    /**
      Returns an array of the parameters this method was called with skipping
      the final parameter because it's the public method options.

      @method _getArgs
      @return {Array} An array of the arguments passed to this method.
    */
    _getArgs: function(args) {
      var lastParam = args[args.length - 1];
      var cut;
      // If there is an object after the callback it's the
      // configuration object for ECS.
      if (Y.Lang.isObject(lastParam) &&
          Y.Lang.isFunction(args[args.length - 2])) {
        cut = -1;
      }
      return Array.prototype.slice.call(args, 0, cut);
    },

    /**
      Generates a random number between 1 and 1000;

      @method _generateRandomNumber
      @return {Integer} A random number.
    */
    _generateRandomNumber: function() {
      return Math.floor((Math.random() * 1000) + 1);
    },

    /**
      Generates a unique key to use for new records.

      @method _generateUniqueKey
      @param {String} type The type of record to create (service, unit, etc).
      @return {Integer} The unique key.
    */
    _generateUniqueKey: function(type) {
      var key = this._generateRandomNumber();
      while (this.changeSet[type + '-' + key] !== undefined) {
        key = this._generateRandomNumber();
      }
      return key;
    },

    /**
      Creates a new record of the appropriate type in the changeSet.

      @method _createNewRecord
      @param {String} type The type of record to create (service, unit, etc).
      @param {Object} command The command that's to be executed lazily.
      @return {String} The newly created record key.
    */
    _createNewRecord: function(type, command) {
      var key = type + '-' + this._generateUniqueKey(type);
      command = this._wrapCallback(command);
      this.changeSet[key] = {
        commands: [command]
      };
      return key;
    },

    /**
      Adds a new command to an existing record.

      @method _addToRecrod
      @param {String} key The key to add the command to.
      @param {Object} command The command that's to be executed lazily.
      @return {String} The key to which the command was added.
    */
    _addToRecord: function(key, command) {
      command = this._wrapCallback(command);
      var changeSet = this.changeSet[key];
      var length = changeSet.commands.push(command);
      // Add next function to execute to previous next() command.
      changeSet.commands[length - 2].next = this._execute.bind(this, command);
      return key;
    },

    /**
      Wraps the last function parameter so that we can be notified when it's
      called.

      @method _wrapCallback
      @param {Object} command The individual command object from the changeSet.
      @return {Array} The args array with the wrapped callback.
    */
    _wrapCallback: function(command) {
      var args = command.args;
      var index = args.length - 1;
      var callback = args[index];
      /* jshint -W040 */
      // Possible strict violation.
      var self = this;
      /**
        Wrapper function for the supplied callback for the command.
        @method _callbackWrapper
      */
      function _callbackWrapper() {
        command.executed = true;
        // `this` here is in context that the callback is called
        //  under. In most cases this will be `env`.
        var result = callback.apply(this, self._getArgs(arguments));
        self.fire('taskComplete', command);
        // Execute the next command.
        if (command.next) {
          command.next();
        }
        return result;
      }
      args[index] = _callbackWrapper;
      return command;
    },

    /**
      Executes the passed in command on the environment.

      @method _execute
      @param {Object} command The individual command object from the changeSet.
    */
    _execute: function(command) {
      var env = this.get('env');
      env[command.method].apply(env, command.args);
    },

    /**
      Starts the processing of all of the top level
      commands stored in the changeSet.

      @method commit
    */
    commit: function() {
      var changeSet = this.changeSet;
      var commands;

      Object.keys(changeSet).forEach(function(key) {
        commands = changeSet[key].commands;
        // Trigger the series to start executing.
        this._execute(commands[0]);
        this.fire('commit', commands);
      }, this);
    },

    /* End ECS methods */

    /* Private environment methods. */

    /**
      Creates a new entry in the queue for creating a new service.

      Receives all the parameters it's public method 'deploy' was called with
      with the exception of the ECS options oject.

      @method _lazyDeploy
      @param {Array} args The arguments to deploy the charm with.
    */
    _lazyDeploy: function(args) {
      var command = {
        method: 'deploy',
        executed: false,
        args: args
      };
      return this._createNewRecord('service', command);
    },

    /**
      Creates a new entry in the queue for setting a services config.

      Receives all the parameters it's public method 'set_config' was called
      with with the exception of the ECS options oject.

      @method _lazySetConfig
      @param {Array} args The arguments to set the config with.
    */
    _lazySetConfig: function(args) {
      var serviceName = args[0];
      var queued = this.changeSet[serviceName];
      var command = {
        method: 'set_config', // This needs to match the method name in env.
        executed: false,
        args: args
      };
      // If it's a queued service then we need to add to that service's record.
      if (queued) {
        return this._addToRecord(serviceName, command);
      }
      return this._createNewRecord('setConfig', command);
    },

    /* End private environment methods. */

    /* Public environment methods. */

    /**
      Calls the environments deploy method or creates a new service record
      in the queue.

      The parameters match the parameters for the env deploy method.

      @method deploy
    */
    deploy: function(charmUrl, serviceName, config, configRaw, numUnits,
                     constraints, toMachine, callback, options) {
      var env = this.get('env'),
          args = this._getArgs(arguments);
      if (options && options.immediate) {
        // Call the deploy method right away bypassing the queue.
        env.deploy.apply(env, args);
      } else {
        this._lazyDeploy(args);
      }
    },

    /**
      Calls the environments set_config method or creates a new set_config
      record in the queue.

      THe parameters match the parameters for the env deploy method.

      @method setConfig
    */
    setConfig: function(serviceName, config, data, serviceConfig, callback,
                        options) {
      var env = this.get('env'),
          args = this._getArgs(arguments);
      if (options && options.immediate) {
        // Need to check that the serviceName is a real service name and not
        // a queued service id before allowing immediate or not.
        if (this.changeSet[serviceName]) {
          throw 'You cannot immediately setConfig on a queued service';
        } else {
          env.set_config.apply(env, args);
        }
      } else {
        this._lazySetConfig(args);
      }
    }

    /* End Public environment methods. */

  }, {
    ATTRS: {
      /**
        Reference to the environment
        @attribute env
      */
      env: {},
      /**
        Reference to the db
        @attribute db
      */
      db: {}
    }
  });

}, '', {
  requires: [
    'base',
    'base-build'
  ]
});
<commit_msg>Convert the changeSet data structure to a flat structure with key references to parent commands.<commit_after>/*
This file is part of the Juju GUI, which lets users view and manage Juju
environments within a graphical interface (https://launchpad.net/juju-gui).
Copyright (C) 2012-2013 Canonical Ltd.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License version 3, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero
General Public License for more details.

You should have received a copy of the GNU Affero General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

'use strict';

YUI.add('environment-change-set', function(Y) {
  var ns = Y.namespace('juju');

  var name = 'environment-change-set';

  ns.EnvironmentChangeSet = Y.Base.create(name, Y.Base, [], {

    /**
      Creates the persistent data storage object.

      @method initializer
    */
    initializer: function() {
      this.changeSet = {};
    },

    /* ECS methods */

    /**
      Returns an array of the parameters this method was called with skipping
      the final parameter because it's the public method options.

      @method _getArgs
      @return {Array} An array of the arguments passed to this method.
    */
    _getArgs: function(args) {
      var lastParam = args[args.length - 1];
      var cut;
      // If there is an object after the callback it's the
      // configuration object for ECS.
      if (Y.Lang.isObject(lastParam) &&
          Y.Lang.isFunction(args[args.length - 2])) {
        cut = -1;
      }
      return Array.prototype.slice.call(args, 0, cut);
    },

    /**
      Generates a random number between 1 and 1000;

      @method _generateRandomNumber
      @return {Integer} A random number.
    */
    _generateRandomNumber: function() {
      return Math.floor((Math.random() * 1000) + 1);
    },

    /**
      Generates a unique key to use for new records.

      @method _generateUniqueKey
      @param {String} type The type of record to create (service, unit, etc).
      @return {Integer} The unique key.
    */
    _generateUniqueKey: function(type) {
      var key = this._generateRandomNumber();
      while (this.changeSet[type + '-' + key] !== undefined) {
        key = this._generateRandomNumber();
      }
      return key;
    },

    /**
      Creates a new record of the appropriate type in the changeSet.

      Each record in the changeSet is an object the following format:

      'parents' {Array} Keys whos commands must finish before it can be run.
      'executed' {Boolean} Weather this command has been executed or not.
      'command' {Object} The env method to call with arguments.

      { parents: ['service-1234'],
        executed: false,
        command: {
          method: 'deploy',
          args: ['foo', 'bar'] } }

      @method _createNewRecord
      @param {String} type The type of record to create (service, unit, etc).
      @param {Object} command The command that's to be executed lazily.
      @return {String} The newly created record key.
    */
    _createNewRecord: function(type, command, parents) {
      var key = type + '-' + this._generateUniqueKey(type);
      this.changeSet[key] = {
        id: key,
        parents: parents,
        executed: false,
        command: command
      };
      this._wrapCallback(this.changeSet[key]);
      return key;
    },

    /**
      Wraps the last function parameter so that we can be notified when it's
      called.

      @method _wrapCallback
      @param {Object} record The individual record object from the changeSet.
    */
    _wrapCallback: function(record) {
      var args = record.command.args;
      var index = args.length - 1;
      var callback = args[index];
      /* jshint -W040 */
      // Possible strict violation.
      var self = this;
      /**
        Wrapper function for the supplied callback for the command.
        @method _callbackWrapper
      */
      function _callbackWrapper() {
        record.executed = true;
        // `this` here is in context that the callback is called
        //  under. In most cases this will be `env`.
        var result = callback.apply(this, self._getArgs(arguments));
        self.fire('taskComplete', {
          id: record.id,
          record: record
        });
        return result;
      }
      args[index] = _callbackWrapper;
    },

    /**
      Executes the passed in record on the environment.

      @method _execute
      @param {Object} record The individual record object from the changeSet.
    */
    _execute: function(record) {
      var env = this.get('env');
      var command = record.command;
      env[command.method].apply(env, command.args);
    },

    /**
      Starts the processing of all of the top level
      commands stored in the changeSet.

      @method commit
    */
    commit: function() {
      var changeSet = this.changeSet;
      var command;

      Object.keys(changeSet).forEach(function(key) {
        command = changeSet[key];
        // XXX Jeff March 24 2014 - This executes all commands from top to
        // bottom. Instead this needs to queue up the tasks based on their
        // hierarchical structure.
        this._execute(command);
        this.fire('commit', command);
      }, this);
    },

    /* End ECS methods */

    /* Private environment methods. */

    /**
      Creates a new entry in the queue for creating a new service.

      Receives all the parameters it's public method 'deploy' was called with
      with the exception of the ECS options oject.

      @method _lazyDeploy
      @param {Array} args The arguments to deploy the charm with.
    */
    _lazyDeploy: function(args) {
      var command = {
        method: 'deploy',
        args: args
      };
      // The 6th param is the toMachine param of the env deploy call.
      var parent = args[6];
      if (!this.changeSet[parent]) {
        // If the toMachine isn't a record in the changeSet that means it's
        // an existing machine and this does not need to be queued behind
        // another command.
        parent = null;
      }
      return this._createNewRecord('service', command, parent);
    },

    /**
      Creates a new entry in the queue for setting a services config.

      Receives all the parameters it's public method 'set_config' was called
      with with the exception of the ECS options oject.

      @method _lazySetConfig
      @param {Array} args The arguments to set the config with.
    */
    _lazySetConfig: function(args) {
      var serviceName = args[0];
      var queued;
      if (this.changeSet[serviceName]) {
        // If it's a queued service then we need to add this command as a
        // reference to the deploy service command.
        queued = [serviceName];
      }
      var command = {
        method: 'set_config', // This needs to match the method name in env.
        args: args
      };
      // XXX Jeff - We may want to flatten this into the deploy service
      // command on 'commit' if there is a queued service for this command.
      // We will want to flatten multiple setConfig calls to the same service
      // on 'commit'.
      return this._createNewRecord('setConfig', command, queued);
    },

    /* End private environment methods. */

    /* Public environment methods. */

    /**
      Calls the environments deploy method or creates a new service record
      in the queue.

      The parameters match the parameters for the public env deploy method in
      go.js.

      @method deploy
    */
    deploy: function(charmUrl, serviceName, config, configRaw, numUnits,
                     constraints, toMachine, callback, options) {
      var env = this.get('env'),
          args = this._getArgs(arguments);
      if (options && options.immediate) {
        // Call the deploy method right away bypassing the queue.
        env.deploy.apply(env, args);
      } else {
        this._lazyDeploy(args);
      }
    },

    /**
      Calls the environments set_config method or creates a new set_config
      record in the queue.

      The parameters match the parameters for the public env deploy method in
      go.js.

      @method setConfig
    */
    setConfig: function(serviceName, config, data, serviceConfig, callback,
                        options) {
      var env = this.get('env'),
          args = this._getArgs(arguments);
      if (options && options.immediate) {
        // Need to check that the serviceName is a real service name and not
        // a queued service id before allowing immediate or not.
        if (this.changeSet[serviceName]) {
          throw 'You cannot immediately setConfig on a queued service';
        } else {
          env.set_config.apply(env, args);
        }
      } else {
        this._lazySetConfig(args);
      }
    }

    /* End Public environment methods. */

  }, {
    ATTRS: {
      /**
        Reference to the environment
        @attribute env
      */
      env: {},
      /**
        Reference to the db
        @attribute db
      */
      db: {}
    }
  });

}, '', {
  requires: [
    'base',
    'base-build'
  ]
});
<|endoftext|>