<commit_before>const Vec3 = require('vec3').Vec3
const assert = require('assert')
const math = require('../math')
const conv = require('../conversions')
const { performance } = require('perf_hooks')
const { callbackify, createDoneTask, createTask } = require('../promise_utils')

const { Physics, PlayerState } = require('prismarine-physics')

module.exports = inject

const PI = Math.PI
const PI_2 = Math.PI * 2
const PHYSICS_INTERVAL_MS = 50
const PHYSICS_TIMESTEP = PHYSICS_INTERVAL_MS / 1000

function inject (bot, { physicsEnabled }) {
  const mcData = require('minecraft-data')(bot.version)

  const world = { getBlock: (pos) => { return bot.blockAt(pos, false) } }
  const physics = Physics(mcData, world)

  bot.jumpQueued = false
  bot.jumpTicks = 0 // autojump cooldown

  const controlState = {
    forward: false,
    back: false,
    left: false,
    right: false,
    jump: false,
    sprint: false,
    sneak: false
  }
  let lastSentYaw = null
  let doPhysicsTimer = null
  let lastPhysicsFrameTime = null
  let shouldUsePhysics = false
  bot.physicsEnabled = physicsEnabled ?? true

  const lastSent = {
    x: 0,
    y: 0,
    z: 0,
    yaw: 0,
    pitch: 0,
    onGround: false,
    time: 0
  }

  // This function should be executed each tick (every 0.05 seconds)
  // How it works: https://gafferongames.com/post/fix_your_timestep/
  let timeAccumulator = 0
  function doPhysics () {
    const now = performance.now()
    const deltaSeconds = (now - lastPhysicsFrameTime) / 1000
    lastPhysicsFrameTime = now

    timeAccumulator += deltaSeconds

    while (timeAccumulator >= PHYSICS_TIMESTEP) {
      if (bot.physicsEnabled && shouldUsePhysics) {
        physics.simulatePlayer(new PlayerState(bot, controlState), world).apply(bot)
        bot.emit('physicsTick')
        bot.emit('physicTick') // Deprecated, only exists to support old plugins. May be removed in the future
      }
      updatePosition(PHYSICS_TIMESTEP)
      timeAccumulator -= PHYSICS_TIMESTEP
    }
  }

  function cleanup () {
    clearInterval(doPhysicsTimer)
    doPhysicsTimer = null
  }

  function sendPacketPosition (position, onGround) {
    // sends data, no logic
    const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
    lastSent.x = position.x
    lastSent.y = position.y
    lastSent.z = position.z
    lastSent.onGround = onGround
    bot._client.write('position', lastSent)
    bot.emit('move', oldPos)
  }

  function sendPacketLook (yaw, pitch, onGround) {
    // sends data, no logic
    const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
    lastSent.yaw = yaw
    lastSent.pitch = pitch
    lastSent.onGround = onGround
    bot._client.write('look', lastSent)
    bot.emit('move', oldPos)
  }

  function sendPacketPositionAndLook (position, yaw, pitch, onGround) {
    // sends data, no logic
    const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
    lastSent.x = position.x
    lastSent.y = position.y
    lastSent.z = position.z
    lastSent.yaw = yaw
    lastSent.pitch = pitch
    lastSent.onGround = onGround
    bot._client.write('position_look', lastSent)
    bot.emit('move', oldPos)
  }

  function deltaYaw (yaw1, yaw2) {
    let dYaw = (yaw1 - yaw2) % PI_2
    if (dYaw < -PI) dYaw += PI_2
    else if (dYaw > PI) dYaw -= PI_2

    return dYaw
  }

  function updatePosition (dt) {
    // If you're dead, you're probably on the ground though ...
    if (!bot.isAlive) bot.entity.onGround = true

    // Increment the yaw in baby steps so that notchian clients (not the server) can keep up.
    const dYaw = deltaYaw(bot.entity.yaw, lastSentYaw)

    // Vanilla doesn't clamp yaw, so we don't want to do it either
    const maxDeltaYaw = dt * physics.yawSpeed
    lastSentYaw += math.clamp(-maxDeltaYaw, dYaw, maxDeltaYaw)

    const yaw = conv.toNotchianYaw(lastSentYaw)
    const pitch = conv.toNotchianPitch(bot.entity.pitch)
    const position = bot.entity.position
    const onGround = bot.entity.onGround

    // Only send a position update if necessary, select the appropriate packet
    const positionUpdated = lastSent.x !== position.x || lastSent.y !== position.y || lastSent.z !== position.z
    const lookUpdated = lastSent.yaw !== yaw || lastSent.pitch !== pitch

    if (positionUpdated && lookUpdated && bot.isAlive) {
      sendPacketPositionAndLook(position, yaw, pitch, onGround)
    } else if (positionUpdated && bot.isAlive) {
      sendPacketPosition(position, onGround)
    } else if (lookUpdated && bot.isAlive) {
      sendPacketLook(yaw, pitch, onGround)
    } else if (performance.now() - lastSent.time >= 1000) {
      // Send a position packet every second, even if no update was made
      sendPacketPosition(position, onGround)
      lastSent.time = performance.now()
    } else if (bot.supportFeature('positionUpdateSentEveryTick') && bot.isAlive) {
      // For versions < 1.12, one player packet should be sent every tick
      // for the server to update health correctly
      bot._client.write('flying', { onGround: bot.entity.onGround })
    }
  }

  bot.physics = physics

  bot.setControlState = (control, state) => {
    assert.ok(control in controlState, `invalid control: ${control}`)
    assert.ok(typeof state === 'boolean', `invalid state: ${state}`)
    if (controlState[control] === state) return
    controlState[control] = state
    if (control === 'jump' && state) {
      bot.jumpQueued = true
    } else if (control === 'sprint') {
      bot._client.write('entity_action', {
        entityId: bot.entity.id,
        actionId: state ? 3 : 4,
        jumpBoost: 0
      })
    } else if (control === 'sneak') {
      bot._client.write('entity_action', {
        entityId: bot.entity.id,
        actionId: state ? 0 : 1,
        jumpBoost: 0
      })
    }
  }

  bot.getControlState = (control) => {
    assert.ok(control in controlState, `invalid control: ${control}`)
    return controlState[control]
  }

  bot.clearControlStates = () => {
    for (const control in controlState) {
      bot.setControlState(control, false)
    }
  }

  bot.controlState = {}

  for (const control of Object.keys(controlState)) {
    Object.defineProperty(bot.controlState, control, {
      get () {
        return controlState[control]
      },
      set (state) {
        bot.setControlState(control, state)
        return state
      }
    })
  }

  let lookingTask = createDoneTask()

  bot.on('move', () => {
    if (!lookingTask.done && Math.abs(deltaYaw(bot.entity.yaw, lastSentYaw)) < 0.001) {
      lookingTask.finish()
    }
  })

  bot._client.on('explosion', explosion => {
    // TODO: emit an explosion event with more info
    if (physicsEnabled && bot.game.gameMode !== 'creative') {
      bot.entity.velocity.x += explosion.playerMotionX
      bot.entity.velocity.y += explosion.playerMotionY
      bot.entity.velocity.z += explosion.playerMotionZ
    }
  })

  bot.look = callbackify(async (yaw, pitch, force) => {
    if (!lookingTask.done) {
      lookingTask.finish() // finish the previous one
    }
    lookingTask = createTask()

    bot.entity.yaw = yaw
    bot.entity.pitch = pitch
    if (force) {
      lastSentYaw = yaw
      return
    }

    await lookingTask.promise
  })

  bot.lookAt = callbackify(async (point, force) => {
    const delta = point.minus(bot.entity.position.offset(0, bot.entity.height, 0))
    const yaw = Math.atan2(-delta.x, -delta.z)
    const groundDistance = Math.sqrt(delta.x * delta.x + delta.z * delta.z)
    const pitch = Math.atan2(delta.y, groundDistance)
    await bot.look(yaw, pitch, force)
  })

  // player position and look (clientbound)
  bot._client.on('position', (packet) => {
    bot.entity.height = 1.62
    bot.entity.velocity.set(0, 0, 0)

    // If flag is set, then the corresponding value is relative, else it is absolute
    const pos = bot.entity.position
    pos.set(
      packet.flags & 1 ? (pos.x + packet.x) : packet.x,
      packet.flags & 2 ? (pos.y + packet.y) : packet.y,
      packet.flags & 4 ? (pos.z + packet.z) : packet.z
    )

    const newYaw = (packet.flags & 8 ? conv.toNotchianYaw(bot.entity.yaw) : 0) + packet.yaw
    const newPitch = (packet.flags & 16 ? conv.toNotchianPitch(bot.entity.pitch) : 0) + packet.pitch
    bot.entity.yaw = conv.fromNotchianYaw(newYaw)
    bot.entity.pitch = conv.fromNotchianPitch(newPitch)
    bot.entity.onGround = false

    if (bot.supportFeature('teleportUsesOwnPacket')) {
      bot._client.write('teleport_confirm', { teleportId: packet.teleportId })
      // Force send an extra packet to be like vanilla client
      sendPacketPositionAndLook(pos, newYaw, newPitch, bot.entity.onGround)
    }
    sendPacketPositionAndLook(pos, newYaw, newPitch, bot.entity.onGround)

    shouldUsePhysics = true
    bot.entity.timeSinceOnGround = 0
    lastSentYaw = bot.entity.yaw

    if (doPhysicsTimer === null) {
      lastPhysicsFrameTime = performance.now()
      doPhysicsTimer = setInterval(doPhysics, PHYSICS_INTERVAL_MS)
    }
    bot.emit('forcedMove')
  })

  bot.waitForTicks = async function (ticks) {
    if (ticks <= 0) return
    await new Promise(resolve => {
      const tickListener = () => {
        ticks--
        if (ticks === 0) {
          bot.removeListener('physicsTick', tickListener)
          resolve()
        }
      }

      bot.on('physicsTick', tickListener)
    })
  }

  bot.on('mount', () => { shouldUsePhysics = false })
  bot.on('respawn', () => { shouldUsePhysics = false })
  bot.on('end', cleanup)
}
<commit_msg>Fixes physicsEnabled check (#2079)<commit_after>const Vec3 = require('vec3').Vec3
const assert = require('assert')
const math = require('../math')
const conv = require('../conversions')
const { performance } = require('perf_hooks')
const { callbackify, createDoneTask, createTask } = require('../promise_utils')

const { Physics, PlayerState } = require('prismarine-physics')

module.exports = inject

const PI = Math.PI
const PI_2 = Math.PI * 2
const PHYSICS_INTERVAL_MS = 50
const PHYSICS_TIMESTEP = PHYSICS_INTERVAL_MS / 1000

function inject (bot, { physicsEnabled }) {
  const mcData = require('minecraft-data')(bot.version)

  const world = { getBlock: (pos) => { return bot.blockAt(pos, false) } }
  const physics = Physics(mcData, world)

  bot.jumpQueued = false
  bot.jumpTicks = 0 // autojump cooldown

  const controlState = {
    forward: false,
    back: false,
    left: false,
    right: false,
    jump: false,
    sprint: false,
    sneak: false
  }
  let lastSentYaw = null
  let doPhysicsTimer = null
  let lastPhysicsFrameTime = null
  let shouldUsePhysics = false
  bot.physicsEnabled = physicsEnabled ?? true

  const lastSent = {
    x: 0,
    y: 0,
    z: 0,
    yaw: 0,
    pitch: 0,
    onGround: false,
    time: 0
  }

  // This function should be executed each tick (every 0.05 seconds)
  // How it works: https://gafferongames.com/post/fix_your_timestep/
  let timeAccumulator = 0
  function doPhysics () {
    const now = performance.now()
    const deltaSeconds = (now - lastPhysicsFrameTime) / 1000
    lastPhysicsFrameTime = now

    timeAccumulator += deltaSeconds

    while (timeAccumulator >= PHYSICS_TIMESTEP) {
      if (bot.physicsEnabled && shouldUsePhysics) {
        physics.simulatePlayer(new PlayerState(bot, controlState), world).apply(bot)
        bot.emit('physicsTick')
        bot.emit('physicTick') // Deprecated, only exists to support old plugins. May be removed in the future
      }
      updatePosition(PHYSICS_TIMESTEP)
      timeAccumulator -= PHYSICS_TIMESTEP
    }
  }

  function cleanup () {
    clearInterval(doPhysicsTimer)
    doPhysicsTimer = null
  }

  function sendPacketPosition (position, onGround) {
    // sends data, no logic
    const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
    lastSent.x = position.x
    lastSent.y = position.y
    lastSent.z = position.z
    lastSent.onGround = onGround
    bot._client.write('position', lastSent)
    bot.emit('move', oldPos)
  }

  function sendPacketLook (yaw, pitch, onGround) {
    // sends data, no logic
    const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
    lastSent.yaw = yaw
    lastSent.pitch = pitch
    lastSent.onGround = onGround
    bot._client.write('look', lastSent)
    bot.emit('move', oldPos)
  }

  function sendPacketPositionAndLook (position, yaw, pitch, onGround) {
    // sends data, no logic
    const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z)
    lastSent.x = position.x
    lastSent.y = position.y
    lastSent.z = position.z
    lastSent.yaw = yaw
    lastSent.pitch = pitch
    lastSent.onGround = onGround
    bot._client.write('position_look', lastSent)
    bot.emit('move', oldPos)
  }

  function deltaYaw (yaw1, yaw2) {
    let dYaw = (yaw1 - yaw2) % PI_2
    if (dYaw < -PI) dYaw += PI_2
    else if (dYaw > PI) dYaw -= PI_2

    return dYaw
  }

  function updatePosition (dt) {
    // If you're dead, you're probably on the ground though ...
    if (!bot.isAlive) bot.entity.onGround = true

    // Increment the yaw in baby steps so that notchian clients (not the server) can keep up.
    const dYaw = deltaYaw(bot.entity.yaw, lastSentYaw)

    // Vanilla doesn't clamp yaw, so we don't want to do it either
    const maxDeltaYaw = dt * physics.yawSpeed
    lastSentYaw += math.clamp(-maxDeltaYaw, dYaw, maxDeltaYaw)

    const yaw = conv.toNotchianYaw(lastSentYaw)
    const pitch = conv.toNotchianPitch(bot.entity.pitch)
    const position = bot.entity.position
    const onGround = bot.entity.onGround

    // Only send a position update if necessary, select the appropriate packet
    const positionUpdated = lastSent.x !== position.x || lastSent.y !== position.y || lastSent.z !== position.z
    const lookUpdated = lastSent.yaw !== yaw || lastSent.pitch !== pitch

    if (positionUpdated && lookUpdated && bot.isAlive) {
      sendPacketPositionAndLook(position, yaw, pitch, onGround)
    } else if (positionUpdated && bot.isAlive) {
      sendPacketPosition(position, onGround)
    } else if (lookUpdated && bot.isAlive) {
      sendPacketLook(yaw, pitch, onGround)
    } else if (performance.now() - lastSent.time >= 1000) {
      // Send a position packet every second, even if no update was made
      sendPacketPosition(position, onGround)
      lastSent.time = performance.now()
    } else if (bot.supportFeature('positionUpdateSentEveryTick') && bot.isAlive) {
      // For versions < 1.12, one player packet should be sent every tick
      // for the server to update health correctly
      bot._client.write('flying', { onGround: bot.entity.onGround })
    }
  }

  bot.physics = physics

  bot.setControlState = (control, state) => {
    assert.ok(control in controlState, `invalid control: ${control}`)
    assert.ok(typeof state === 'boolean', `invalid state: ${state}`)
    if (controlState[control] === state) return
    controlState[control] = state
    if (control === 'jump' && state) {
      bot.jumpQueued = true
    } else if (control === 'sprint') {
      bot._client.write('entity_action', {
        entityId: bot.entity.id,
        actionId: state ? 3 : 4,
        jumpBoost: 0
      })
    } else if (control === 'sneak') {
      bot._client.write('entity_action', {
        entityId: bot.entity.id,
        actionId: state ? 0 : 1,
        jumpBoost: 0
      })
    }
  }

  bot.getControlState = (control) => {
    assert.ok(control in controlState, `invalid control: ${control}`)
    return controlState[control]
  }

  bot.clearControlStates = () => {
    for (const control in controlState) {
      bot.setControlState(control, false)
    }
  }

  bot.controlState = {}

  for (const control of Object.keys(controlState)) {
    Object.defineProperty(bot.controlState, control, {
      get () {
        return controlState[control]
      },
      set (state) {
        bot.setControlState(control, state)
        return state
      }
    })
  }

  let lookingTask = createDoneTask()

  bot.on('move', () => {
    if (!lookingTask.done && Math.abs(deltaYaw(bot.entity.yaw, lastSentYaw)) < 0.001) {
      lookingTask.finish()
    }
  })

  bot._client.on('explosion', explosion => {
    // TODO: emit an explosion event with more info
    if (bot.physicsEnabled && bot.game.gameMode !== 'creative') {
      bot.entity.velocity.x += explosion.playerMotionX
      bot.entity.velocity.y += explosion.playerMotionY
      bot.entity.velocity.z += explosion.playerMotionZ
    }
  })

  bot.look = callbackify(async (yaw, pitch, force) => {
    if (!lookingTask.done) {
      lookingTask.finish() // finish the previous one
    }
    lookingTask = createTask()

    bot.entity.yaw = yaw
    bot.entity.pitch = pitch
    if (force) {
      lastSentYaw = yaw
      return
    }

    await lookingTask.promise
  })

  bot.lookAt = callbackify(async (point, force) => {
    const delta = point.minus(bot.entity.position.offset(0, bot.entity.height, 0))
    const yaw = Math.atan2(-delta.x, -delta.z)
    const groundDistance = Math.sqrt(delta.x * delta.x + delta.z * delta.z)
    const pitch = Math.atan2(delta.y, groundDistance)
    await bot.look(yaw, pitch, force)
  })

  // player position and look (clientbound)
  bot._client.on('position', (packet) => {
    bot.entity.height = 1.62
    bot.entity.velocity.set(0, 0, 0)

    // If flag is set, then the corresponding value is relative, else it is absolute
    const pos = bot.entity.position
    pos.set(
      packet.flags & 1 ? (pos.x + packet.x) : packet.x,
      packet.flags & 2 ? (pos.y + packet.y) : packet.y,
      packet.flags & 4 ? (pos.z + packet.z) : packet.z
    )

    const newYaw = (packet.flags & 8 ? conv.toNotchianYaw(bot.entity.yaw) : 0) + packet.yaw
    const newPitch = (packet.flags & 16 ? conv.toNotchianPitch(bot.entity.pitch) : 0) + packet.pitch
    bot.entity.yaw = conv.fromNotchianYaw(newYaw)
    bot.entity.pitch = conv.fromNotchianPitch(newPitch)
    bot.entity.onGround = false

    if (bot.supportFeature('teleportUsesOwnPacket')) {
      bot._client.write('teleport_confirm', { teleportId: packet.teleportId })
      // Force send an extra packet to be like vanilla client
      sendPacketPositionAndLook(pos, newYaw, newPitch, bot.entity.onGround)
    }
    sendPacketPositionAndLook(pos, newYaw, newPitch, bot.entity.onGround)

    shouldUsePhysics = true
    bot.entity.timeSinceOnGround = 0
    lastSentYaw = bot.entity.yaw

    if (doPhysicsTimer === null) {
      lastPhysicsFrameTime = performance.now()
      doPhysicsTimer = setInterval(doPhysics, PHYSICS_INTERVAL_MS)
    }
    bot.emit('forcedMove')
  })

  bot.waitForTicks = async function (ticks) {
    if (ticks <= 0) return
    await new Promise(resolve => {
      const tickListener = () => {
        ticks--
        if (ticks === 0) {
          bot.removeListener('physicsTick', tickListener)
          resolve()
        }
      }

      bot.on('physicsTick', tickListener)
    })
  }

  bot.on('mount', () => { shouldUsePhysics = false })
  bot.on('respawn', () => { shouldUsePhysics = false })
  bot.on('end', cleanup)
}
<|endoftext|>


<commit_before>/*
 * Copyright 2013 Joyent, Inc.  All rights reserved.
 *
 * Restify handlers for firewall rules
 */

var clone = require('clone');
var filter = require('../ufds/filter');
var Rule = require('../fwrule').Rule;
var restify = require('restify');
var ufdsmodel = require('../ufds/ufdsmodel');
var util = require('util');

// --- Internal helpers

function populateConstructorData(req, data) {
  if (req._datacenter) {
    data.datacenter = req._datacenter;
  }
  data.user = req.params.owner_uuid;
}

/**
 * Validate shared request params
 */
function validateReqParams(req, res, next) {
  // XXX: validate :uuid, enabled here

  /*
  if (!req.params.owner_uuid) {
    return next(new restify.MissingParameterError(
          '"owner_uuid" parameter required'));
  }
  */

  return next();
}

// --- Restify handlers

/**
 * GET /rules
 */
function listRules(req, res, next) {
  var ruleFilter;
  try {
    ruleFilter = filter.rules(req.params, req.log);
  } catch (err) {
    return next(err);
  }

  return ufdsmodel.requestListFiltered(req, res, next, Rule, ruleFilter);
}

/**
 * GET /rules/:uuid
 */
function getRule(req, res, next) {
  return ufdsmodel.requestGet(req, res, next, Rule);
}

/**
 * POST /rules/:uuid
 */
function createRule(req, res, next) {
  if (!req.params.rule) {
    return next(new restify.MissingParameterError(
          '"rule" parameter required'));
  }

  req.params.datacenter = req._datacenter;
  var rule;

  try {
    rule = new Rule(req.params);
  } catch (err) {
    return next(err);
  }

  var params = {
    dn: rule.dn,
    target: 'add-' + rule.uuid,
    fwapiTags: rule.tags,
    task: 'add',
    rule: rule.serialize(),
    ufdsRaw: clone(rule.raw()),
    fwapiVMs: rule.vms
  };

  req._wfapi.createJob('add', params, function (err, job) {
    if (err) {
      return next(err);
    }

    req.log.debug({ params: params, job: job },
      'Add job "%s" queued for rule "%s"', job.uuid, params.ufdsRaw.uuid);

    res.send(202, {
      job_uuid: job.uuid,
      rule: rule.serialize()
    });

    return next();
  });
}

/**
 * PUT /rules/:uuid
 */
function updateRule(req, res, next) {
  var dn;
  req.params.datacenter = req._datacenter;
  req.params.user = req.params.owner_uuid;

  try {
    dn = Rule.dnFromRequest(req);
  } catch (err) {
    req.log.error(err, 'Error creating <Rule> dn');
    return next(err);
  }

  // We need to do a get first in case we don't have the rule text, which is
  // used to determine the vms and tags so that we know what servers to
  // send to
  ufdsmodel.modelGet(req._app, Rule, dn, req.log, function (err, rule) {
    var vms = {};
    var tags = {};

    // We need a list of all tags before and after (in case we've updated a
    // rule so that it removes them)
    rule.tags.forEach(function (t) {
      tags[t] = 1;
    });
    rule.vms.forEach(function (v) {
      vms[v] = 1;
    });

    var newRule;
    try {
      newRule = new Rule(clone(req.params));
    } catch (e) {
      return next(e);
    }

    newRule.tags.forEach(function (t) {
      tags[t] = 1;
    });
    newRule.vms.forEach(function (v) {
      vms[v] = 1;
    });

    var params = {
      dn: dn,
      rule: newRule.serialize(),
      fwapiTags: Object.keys(tags),
      target: 'update-' + newRule.uuid,
      task: 'update',
      ufdsRaw: clone(newRule.raw()),
      fwapiVMs: Object.keys(vms)
    };

    req._wfapi.createJob('update', params, function (err, job) {
      if (err) {
        return next(err);
      }

      req.log.debug({ params: params, job: job },
        'Update job "%s" queued for rule "%s"', job.uuid, req.params.uuid);

      res.send(200, {
        job_uuid: job.uuid
      });

      return next();
    });
  });

}

/**
 * DELETE /rules/:uuid
 */
function deleteRule(req, res, next) {
  var dn;
  req.params.datacenter = req._datacenter;

  try {
    dn = Rule.dnFromRequest(req);
  } catch (err) {
    req.log.error(err, 'Error creating <Rule> dn');
    return next(err);
  }

  ufdsmodel.modelGet(req._app, Rule, dn, req.log, function (err, rule) {
    if (err) {
      return next(err);
    }

    var params = {
      dn: dn,
      rule_uuid: rule.uuid,
      fwapiTags: rule.tags,
      target: 'del-' + rule.uuid,
      task: 'del',
      fwapiVMs: rule.vms
    };

    req._wfapi.createJob('del', params, function (err, job) {
      if (err) {
        return next(err);
      }

      req.log.debug({ params: params, job: job },
        'Del job "%s" queued for rule "%s"', job.uuid, req.params.uuid);

      res.send(200, {
        job_uuid: job.uuid
      });

      return next();
    });
  });
}

// --- Exports

/**
 * Registers endpoints with a restify server
 */
function register(server, before) {
  var userRequired = before.concat(validateReqParams);

  server.get({ path: '/rules', name: 'listRules' }, before, listRules);
  server.post({ path: '/rules', name: 'createRule' }, userRequired,
      createRule);
  server.put({ path: '/rules/:uuid', name: 'updateRule' },
      userRequired, updateRule);
  server.get({ path: '/rules/:uuid', name: 'getRule' }, userRequired,
      getRule);
  server.del({ path: '/rules/:uuid', name: 'deleteRule' },
      userRequired, deleteRule);
}

module.exports = {
  register: register
};
<commit_msg>[FWAPI-22] It's not possible to enable/disable a rule w/o specifying the whole rule text<commit_after>/*
 * Copyright 2013 Joyent, Inc.  All rights reserved.
 *
 * Restify handlers for firewall rules
 */

var clone = require('clone');
var filter = require('../ufds/filter');
var Rule = require('../fwrule').Rule;
var restify = require('restify');
var ufdsmodel = require('../ufds/ufdsmodel');
var util = require('util');

// --- Internal helpers

function populateConstructorData(req, data) {
  if (req._datacenter) {
    data.datacenter = req._datacenter;
  }
  data.user = req.params.owner_uuid;
}

/**
 * Validate shared request params
 */
function validateReqParams(req, res, next) {
  // XXX: validate :uuid, enabled here

  /*
  if (!req.params.owner_uuid) {
    return next(new restify.MissingParameterError(
          '"owner_uuid" parameter required'));
  }
  */

  return next();
}

// --- Restify handlers

/**
 * GET /rules
 */
function listRules(req, res, next) {
  var ruleFilter;
  try {
    ruleFilter = filter.rules(req.params, req.log);
  } catch (err) {
    return next(err);
  }

  return ufdsmodel.requestListFiltered(req, res, next, Rule, ruleFilter);
}

/**
 * GET /rules/:uuid
 */
function getRule(req, res, next) {
  return ufdsmodel.requestGet(req, res, next, Rule);
}

/**
 * POST /rules/:uuid
 */
function createRule(req, res, next) {
  if (!req.params.rule) {
    return next(new restify.MissingParameterError(
          '"rule" parameter required'));
  }

  req.params.datacenter = req._datacenter;
  var rule;

  try {
    rule = new Rule(req.params);
  } catch (err) {
    return next(err);
  }

  var params = {
    dn: rule.dn,
    target: 'add-' + rule.uuid,
    fwapiTags: rule.tags,
    task: 'add',
    rule: rule.serialize(),
    ufdsRaw: clone(rule.raw()),
    fwapiVMs: rule.vms
  };

  req._wfapi.createJob('add', params, function (err, job) {
    if (err) {
      return next(err);
    }

    req.log.debug({ params: params, job: job },
      'Add job "%s" queued for rule "%s"', job.uuid, params.ufdsRaw.uuid);

    res.send(202, {
      job_uuid: job.uuid,
      rule: rule.serialize()
    });

    return next();
  });
}

/**
 * PUT /rules/:uuid
 */
function updateRule(req, res, next) {
  var dn;
  req.params.datacenter = req._datacenter;
  req.params.user = req.params.owner_uuid;

  try {
    dn = Rule.dnFromRequest(req);
  } catch (err) {
    req.log.error(err, 'Error creating <Rule> dn');
    return next(err);
  }

  // We need to do a get first in case we don't have the rule text, which is
  // used to determine the vms and tags so that we know what servers to
  // send to
  ufdsmodel.modelGet(req._app, Rule, dn, req.log, function (err, rule) {
    var vms = {};
    var tags = {};

    // We need a list of all tags before and after (in case we've updated a
    // rule so that it removes them)
    rule.tags.forEach(function (t) {
      tags[t] = 1;
    });
    rule.vms.forEach(function (v) {
      vms[v] = 1;
    });

    var newRule;
    var updateParams = clone(rule.serialize());
    for (var p in req.params) {
      updateParams[p] = req.params[p];
    }

    try {
      newRule = new Rule(updateParams);
    } catch (e) {
      return next(e);
    }

    newRule.tags.forEach(function (t) {
      tags[t] = 1;
    });
    newRule.vms.forEach(function (v) {
      vms[v] = 1;
    });

    var params = {
      dn: dn,
      rule: newRule.serialize(),
      fwapiTags: Object.keys(tags),
      target: 'update-' + newRule.uuid,
      task: 'update',
      ufdsRaw: clone(newRule.raw()),
      fwapiVMs: Object.keys(vms)
    };

    req._wfapi.createJob('update', params, function (err, job) {
      if (err) {
        return next(err);
      }

      req.log.debug({ params: params, job: job },
        'Update job "%s" queued for rule "%s"', job.uuid, req.params.uuid);

      res.send(200, {
        job_uuid: job.uuid
      });

      return next();
    });
  });

}

/**
 * DELETE /rules/:uuid
 */
function deleteRule(req, res, next) {
  var dn;
  req.params.datacenter = req._datacenter;

  try {
    dn = Rule.dnFromRequest(req);
  } catch (err) {
    req.log.error(err, 'Error creating <Rule> dn');
    return next(err);
  }

  ufdsmodel.modelGet(req._app, Rule, dn, req.log, function (err, rule) {
    if (err) {
      return next(err);
    }

    var params = {
      dn: dn,
      rule_uuid: rule.uuid,
      fwapiTags: rule.tags,
      target: 'del-' + rule.uuid,
      task: 'del',
      fwapiVMs: rule.vms
    };

    req._wfapi.createJob('del', params, function (err, job) {
      if (err) {
        return next(err);
      }

      req.log.debug({ params: params, job: job },
        'Del job "%s" queued for rule "%s"', job.uuid, req.params.uuid);

      res.send(200, {
        job_uuid: job.uuid
      });

      return next();
    });
  });
}

// --- Exports

/**
 * Registers endpoints with a restify server
 */
function register(server, before) {
  var userRequired = before.concat(validateReqParams);

  server.get({ path: '/rules', name: 'listRules' }, before, listRules);
  server.post({ path: '/rules', name: 'createRule' }, userRequired,
      createRule);
  server.put({ path: '/rules/:uuid', name: 'updateRule' },
      userRequired, updateRule);
  server.get({ path: '/rules/:uuid', name: 'getRule' }, userRequired,
      getRule);
  server.del({ path: '/rules/:uuid', name: 'deleteRule' },
      userRequired, deleteRule);
}

module.exports = {
  register: register
};
<|endoftext|>


<commit_before>// PROTOTYPE, WILL CHANGE
var util = require('util')
var Entity = require('../Entity')
var request = require('request')

// TODO fail on bad stats

// Main class that represents this module. Control inheritance below.
var Main = function(config) {
	if (!(this instanceof Main)) return new Main()
	Entity.call(this,config)

	// Reference object in method context
	var self = this

	console.assert(this.state.hostname,'Server test: hostname must be set')

	// DON'T FORGET TIMEOUT!
	this.interval = 3

	this.test = function() {
		request({
			url:'http://'+self.state.hostname+':2010',
			timeout:1000*self.interval/2,
		},function(err,res,body) {
			if (!err && res.statusCode == 200) {
				var data = JSON.parse(body)
				if(!data.error) {
					self.update(data)
					if(data['Temperature-C'] > 80)
						self.state.fail('Overheat alert')
					else if (data['Storage-GB']/data['StorageTotal-GB'] > 0.9)
						self.state.fail('Disk space alert')
					else
						self.state.pass() // TODO check stats
				} else
					self.state.fail(res.error)
			} else if (err) {
				if (err.message == 'getaddrinfo ENOTFOUND')
					err.message = 'Missing DNS entry'
				else if (err.message == 'ETIMEDOUT')
					err.message = 'Server offline'
				else if (err.message == 'connect ECONNREFUSED')
					err.message = 'Gremlin status reporter not running'

				self.state.fail(err)
			} else
				self.state.fail('HTTP Error '+res.statusCode)
		})
	}

	this.update = function(data) {
		for (var i in data)
			self.state.set(i,data[i])
	}
}

// Inheritance
util.inherits(Main, Entity)
// Exposure
module.exports = Main

//var foo = new Main({class:'wat',category:'ds',name:'sds',url:'http://darksky.io'})
<commit_msg>Server: fix error forwarding<commit_after>// PROTOTYPE, WILL CHANGE
var util = require('util')
var Entity = require('../Entity')
var request = require('request')

// TODO fail on bad stats

// Main class that represents this module. Control inheritance below.
var Main = function(config) {
	if (!(this instanceof Main)) return new Main()
	Entity.call(this,config)

	// Reference object in method context
	var self = this

	console.assert(this.state.hostname,'Server test: hostname must be set')

	// DON'T FORGET TIMEOUT!
	this.interval = 3

	this.test = function() {
		request({
			url:'http://'+self.state.hostname+':2010',
			timeout:1000*self.interval/2,
		},function(err,res,body) {
			if (!err && res.statusCode == 200) {
				var data = JSON.parse(body)
				if(!data.error) {
					self.update(data)
					if(data['Temperature-C'] > 80)
						self.state.fail('Overheat alert')
					else if (data['Storage-GB']/data['StorageTotal-GB'] > 0.9)
						self.state.fail('Disk space alert')
					else
						self.state.pass() // TODO check stats
				} else
					self.state.fail(data.error)
			} else if (err) {
				if (err.message == 'getaddrinfo ENOTFOUND')
					err.message = 'Missing DNS entry'
				else if (err.message == 'ETIMEDOUT')
					err.message = 'Server offline'
				else if (err.message == 'connect ECONNREFUSED')
					err.message = 'Gremlin status reporter not running'

				self.state.fail(err)
			} else
				self.state.fail('HTTP Error '+res.statusCode)
		})
	}

	this.update = function(data) {
		for (var i in data)
			self.state.set(i,data[i])
	}
}

// Inheritance
util.inherits(Main, Entity)
// Exposure
module.exports = Main

//var foo = new Main({class:'wat',category:'ds',name:'sds',url:'http://darksky.io'})
<|endoftext|>


<commit_before>module.exports = {
    'attr-bans': [
        'align',
        'background',
        'bgcolor',
        'border',
        'frameborder',
        'longdesc',
        'marginwidth',
        'marginheight',
        'scrolling',
        'style',
        'width'
    ],
    'indent-style': 'nonmixed',
    'indent-width': 4,
    'indent-width-cont': false,
    'spec-char-escape': true,
    'tag-bans': ['style', 'b', 'i'],
    'tag-close': true,
    'tag-name-lowercase': true,
    'tag-name-match': true,
    'tag-self-close': false,
    'doctype-first': false,
    'doctype-html5': false,
    'attr-name-style': 'lowercase',
    'attr-name-ignore-regex': false,
    'attr-no-dup': true,
    'attr-no-unsafe-char': true,
    'attr-order': false,
    'attr-quote-style': 'double',
    'attr-req-value': true,
    'attr-new-line': false,
    'id-no-dup': true,
    'id-class-no-ad': true,
    'id-class-style': 'underscore',
    'class-no-dup': true,
    'class-style': false,
    'id-class-ignore-regex': false,
    'img-req-alt': true,
    'img-req-src': true,
    'html-valid-content-model': true,
    'head-valid-content-model': true,
    'href-style': false,
    'label-req-for': true,
    'line-end-style': 'lf',
    'line-max-len': false,
    'line-max-len-ignore-regex': false,
    'head-req-title': true,
    'title-no-dup': true,
    'title-max-len': 60,
    'html-req-lang': false,
    'lang-style': 'case',
    'fig-req-figcaption' : false,
    'focusable-tabindex-style' : false,
    'input-radio-req-name': true,
    'input-req-label': false,
    'table-req-caption': false,
    'table-req-header': false
};
<commit_msg>Change attr-name-style default to "dash" (fixes #204)<commit_after>module.exports = {
    'attr-bans': [
        'align',
        'background',
        'bgcolor',
        'border',
        'frameborder',
        'longdesc',
        'marginwidth',
        'marginheight',
        'scrolling',
        'style',
        'width'
    ],
    'indent-style': 'nonmixed',
    'indent-width': 4,
    'indent-width-cont': false,
    'spec-char-escape': true,
    'tag-bans': ['style', 'b', 'i'],
    'tag-close': true,
    'tag-name-lowercase': true,
    'tag-name-match': true,
    'tag-self-close': false,
    'doctype-first': false,
    'doctype-html5': false,
    'attr-name-style': 'dash',
    'attr-name-ignore-regex': false,
    'attr-no-dup': true,
    'attr-no-unsafe-char': true,
    'attr-order': false,
    'attr-quote-style': 'double',
    'attr-req-value': true,
    'attr-new-line': false,
    'id-no-dup': true,
    'id-class-no-ad': true,
    'id-class-style': 'underscore',
    'class-no-dup': true,
    'class-style': false,
    'id-class-ignore-regex': false,
    'img-req-alt': true,
    'img-req-src': true,
    'html-valid-content-model': true,
    'head-valid-content-model': true,
    'href-style': false,
    'label-req-for': true,
    'line-end-style': 'lf',
    'line-max-len': false,
    'line-max-len-ignore-regex': false,
    'head-req-title': true,
    'title-no-dup': true,
    'title-max-len': 60,
    'html-req-lang': false,
    'lang-style': 'case',
    'fig-req-figcaption' : false,
    'focusable-tabindex-style' : false,
    'input-radio-req-name': true,
    'input-req-label': false,
    'table-req-caption': false,
    'table-req-header': false
};
<|endoftext|>


<commit_before>define([
  'backbone',
  'handlebars',
  'd3',
  'underscore',
  '_string',
  'insights/views/InsightsGladAlertsChartView',
  'views/ShareView',
  'views/SourceModalView',
  'helpers/NumbersHelper',
  'text!insights/templates/insights-glad-alerts.handlebars',
  'text!insights/templates/insights-glad-alerts-legend.handlebars',
], function(Backbone, Handlebars, d3, _, _string, InsightsGladAlertsChart,
  ShareView, SourceModalView, NumbersHelper, tpl, tplLegend) {

  'use strict';

  var API = window.gfw.config.GFW_API_HOST_V2;
  var ENDPOINT_CONFIG = '/query/b0c709f0-d1a6-42a0-a750-df8bdb0895f3?sql=SELECT * FROM data';
  var ENDPOINT_DATA = '/query/9ed18255-89a9-4ccd-bdd6-fe7ffa1b1595?sql=SELECT sum(alerts::int) AS alerts, sum(cumulative_emissions::float) AS cumulative_emissions, sum(cumulative_emissions::float) AS cumulative_emissions, sum(above_ground_carbon_loss::float) AS above_ground_carbon_loss, sum(percent_to_emissions_target::float) AS percent_to_emissions_target, sum(percent_to_deforestation_target::float) AS percent_to_deforestation_target, sum(loss::float) AS loss, sum(cumulative_deforestation::float) AS cumulative_deforestation, year::text as year, country_iso, week FROM data WHERE ((country_iso IN (\'%s\') OR state_iso IN (\'%s\')) AND year IN (\'%s\') AND week::int < 52) GROUP BY year, country_iso, week ORDER BY week::int ASC';

  var WEEKS_YEAR = 52;

  var InsightsGladAlerts = Backbone.View.extend({

    events: {
      'click .js-selector': '_changeVisualizations',
      'click .js-share': '_openShare',
      'click .js-year-nav': '_changeYear',
      'change .js-country-selector': '_changeDataByCountry',
      'change .js-year-selector': '_changeDataByYear'
    },

    el: '#insights',

    template: Handlebars.compile(tpl),

    templateLegend: Handlebars.compile(tplLegend),

    defaults: {
      selectedClassEl: '-selected',
      filter: 'carbon_emissions',
      country: 'BRA',
      year: 2016,
      weekStep: 1,
      desforestationFilter: 'deforestation',
      loadingClassEl: 'is-loading',
      mainVisSwitchEl: 'main-vis-switch',
      loadedClassEl: 'loaded',
      countryLabelClassEl: 'js-country-label',
      countrySelectorClassEl: 'js-country-selector',
      legendSelectoClassEl: 'js-legend',
      noDataClassEl: 'no-data',
      imageURI: window.gfw.config.GFW_DATA_S3 + 'climate/glad_maps'
    },

    initialize: function() {
      this.legends = [];
      this.chartConfig = [];
      this.locations = [];
      this.images = {};
      this.currentStep = this.defaults.weekStep;
      this.currentCountry = this.defaults.country;
      this.currentYear = this.defaults.year;
      this.filter = this.defaults.filter;
      this.imageURI = this.defaults.imageURI;

      // Get the visualization's configuration
      $.when(this._getConfig())
        .done(this._initVisualization.bind(this));
    },

    _getConfig: function() {
      return $.ajax({
        url: API + ENDPOINT_CONFIG,
        type: 'GET'
      });
    },

    _initVisualization: function(config) {
      this.config = this._parseConfig(config);
      this.render();
      this._setListeners();

      // Info window
      new SourceModalView();
    },

    _parseConfig: function(config) {
      var data = config && config.data &&
      config.data[0] ? config.data[0] : {};

      if (data) {
        this.chartConfig = JSON.parse(data.vizsetup);
        this.locations = JSON.parse(data.locations);
      }
    },

    render: function() {
      this.$el.html(this.template({
        locations: this.locations
      }));
      this.$el.removeClass(this.defaults.loadingClassEl);
      this.$el.addClass(this.defaults.loadedClassEl);

      // Set default selection from the config
      var defaultCountry = _.findWhere(this.locations, {
        iso: this.chartConfig.defaultSelection
      });

      if (defaultCountry) {
        this._setCurrentCountry(defaultCountry.name, defaultCountry.iso);
      }

      this._renderMainChart();
    },

    _setListeners: function() {
      Backbone.Events.on('insights:glad:update', this._updateLegends.bind(this));
    },

    _renderMainChart: function() {
      var $el = this.el.querySelector('#visMain');
      var $vis = $el.querySelector('.visualization');
      var $chartEl = $el.querySelector('.chart');
      var iso = this.currentCountry;
      var year = this.currentYear;

      this._clearVisualization();

      $chartEl.innerHTML = '';
      $vis.classList.add(this.defaults.loadingClassEl);

      $.ajax({
        url: API + _.str.sprintf(ENDPOINT_DATA, iso, iso, year),
        type: 'GET',
        success: function(res) {
          var data = res.data;

          if (data.length) {
            $el.classList.remove(this.defaults.noDataClassEl);
            this._createVisualization(data);
          } else {
            $el.classList.add(this.defaults.noDataClassEl);
            this._renderNoDataPlaceHolder();
          }

          $vis.classList.remove(this.defaults.loadingClassEl);
        }.bind(this)
      });
    },

    _createVisualization: function(data) {
      var el = this.el.querySelector('#visMain');
      var chartEl = el.querySelector('.chart');
      var legendEl = el.querySelector('.legend');

      this._setMaxWeek(data);

      this.visMain = new InsightsGladAlertsChart({
        el: chartEl,
        params: {
          data: this._parseData(data),
          filter: this.filter,
          currentStep: this.currentStep,
          iso: this.currentCountry,
          year: this.currentYear,
          desforestationFilter: this.defaults.desforestationFilter,
        }
      });

      el.classList.remove(this.defaults.loadingClassEl);
      this._createLegend(legendEl, data, 'main');
    },

    _renderNoDataPlaceHolder: function() {
      var el = this.el.querySelector('#visMain');
      var chartEl = el.querySelector('.chart');

      chartEl.innerHTML = 'There\'s no data available for this selection';
    },

    _parseData: function(data) {
      return _.map(data, function(d) {
        var locationData = _.findWhere(this.locations, {
          iso: this.currentCountry
        });

        if (locationData) {
          d.carbon_average = locationData.targets['carbon_emissions'].average;
          d.carbon_target = locationData.targets['carbon_emissions'].target;
          d.deforestation_average = locationData.targets['deforestation'].average;
          d.deforestation_target = locationData.targets['deforestation'].target;
        }

        if (d.iso === this.chartConfig.defaultSelection) {
          d.selected = true;
        }

        return d;
      }.bind(this));
    },

    _setMaxWeek: function(data) {
      var lastValue = data && data[data.length - 1] ?
        data[data.length - 1] : [];

      if (lastValue) {
        this.currentStep = (lastValue.week * 1);
      }
    },

    _getIso: function() {
      var country = this.currentCountry;
      var re = /(\d+)/g;
      var subst = '-$1';

      country = country.replace(re, subst);
      return country;
    },

    _createLegend: function(el, data, category) {
      this.legends.push({
        element: el,
        data: data,
        category: category,
        filter: this.filter
      });

      this._renderLegend(el, data, this.filter);
    },

    _renderLegend: function(el, data, filter) {
      var current = _.filter(data, function(d) {
        return (d.week * 1) === this.currentStep;
      }.bind(this))[0];

      if (current) {
        var target = Math.round(current.carbon_target * 1);
        var emissions = Math.round(current.cumulative_emissions * 1);
        var deforestation = Math.round(current.cumulative_deforestation * 1);
        var target_deforestation = Math.round(current.deforestation_target * 1);
        var alerts = Math.round(current.alerts);
        var annual_budget = Math.round(((emissions / target) * 100));
        var annual_budget_deforestation = Math.round(((deforestation / target_deforestation) * 100));
        var co2Equivalency = Math.round(((current.above_ground_carbon_loss * 1) * 10000000) / 4.7);

        var date = moment.utc().year(this.currentYear);
        var weeksInYear = date.weeksInYear();
        var currentWeek = weeksInYear > WEEKS_YEAR ? (this.currentStep + 1) : this.currentStep;
        var dateWithWeek = date.week(currentWeek);

        if (weeksInYear > WEEKS_YEAR) {
          dateWithWeek.subtract(1, 'days');
        } else {
          dateWithWeek.add(1, 'days');
        }

        var begin = dateWithWeek.clone().format('YYYY-MM-DD');
        var end = dateWithWeek.clone().add(6, 'days').format('YYYY-MM-DD');

        el.innerHTML = this.templateLegend({
          isDesforestation: filter === this.defaults.desforestationFilter,
          annual_budget:  NumbersHelper.addNumberDecimals(annual_budget),
          annual_budget_deforestation: NumbersHelper.addNumberDecimals(annual_budget_deforestation),
          alerts: NumbersHelper.addNumberDecimals(alerts),
          co2Equivalency: NumbersHelper.addNumberDecimals(co2Equivalency),
          begin: begin,
          end: end,
          iso: this._getIso(),
          week: this.currentStep
        });

        this.legendImage = el.querySelector('.image');
        this.showImage();
      }
    },

    _updateLegends: function(state) {
      if (this.currentStep !== state.step) {
        this.currentStep = state.step;
        this.maxData = state.maxData;
        this.legends.forEach(function(legend) {
          this._renderLegend(legend.element, legend.data, legend.filter);
        }.bind(this));
      }
    },

    _changeVisualizations: function(ev) {
      var current = ev.currentTarget;
      var filter = current.dataset.filter;

      this._changeMainVis(filter);
    },

    _changeMainVis: function(filter) {
      this._toggleFilter(this.defaults.mainVisSwitchEl, filter);

      this.visMain.updateByFilter(filter);

      var legend = _.findWhere(this.legends, {
        category: 'main'
      });
      legend.filter = filter;

      this._renderLegend(legend.element, legend.data, legend.filter);
    },

    _clearVisualization: function() {
      if (this.visMain) {
        this.visMain.remove();
        this.legends = [];

        var legend = this.el.querySelector('.' + this.defaults.legendSelectoClassEl);
        legend.innerHTML = '';
      }
    },

    _changeDataByCountry: function(ev) {
      var current = ev.currentTarget;
      var value = current.value;
      var selected = current.querySelector('[data-iso="'+ value +'"]');

      this._setCurrentCountry(selected.text, value);
      this._renderMainChart();
    },

    _setCurrentCountry: function(text, iso) {
      var label = this.el.querySelector('.' + this.defaults.countryLabelClassEl);
      var selector = this.el.querySelector('.' + this.defaults.countrySelectorClassEl);
      label.innerHTML = text;
      selector.value = iso;

      this.currentCountry = iso;
    },

    _changeDataByYear: function(ev) {
      var current = ev.currentTarget;
      this.currentYear = parseInt(current.value, 10);

      this._renderMainChart();
    },

    _changeYear: function(ev) {
      var action = ev.currentTarget.dataset.action;
      var $selector = ev.currentTarget.parentNode.querySelector('.js-year-selector');
      var current = $selector.selectedIndex;
      var max = $selector.length - 1;
      var newIndex = current;

      if (action === 'add') {
        newIndex++;
      } else if (action === 'sub') {
        newIndex--;
      }

      if (newIndex < 0) {
        newIndex = max;
      } else if (newIndex > max) {
        newIndex = 0;
      }
      $selector.selectedIndex = newIndex;

    },

    _toggleFilter: function(element, filter) {
      var parent = this.el.querySelector('.' + element);
      var selected = parent.querySelector('.' + this.defaults.selectedClassEl);
      var newSelection = parent.querySelector('[data-filter="'+ filter +'"]');

      selected.classList.remove(this.defaults.selectedClassEl);
      newSelection.classList.add(this.defaults.selectedClassEl);
      this.filter = filter;
    },

    _openShare: function(event) {
      var shareView = new ShareView().share(event);
      $('body').append(shareView.el);
    },

    /**
     * Shows the image of the current step
     */
    showImage: function() {
      var maxData = this.maxData || this.currentStep;

      if (!this.images[this.currentStep] &&
        (this.currentStep <= maxData)) {
        var image = new Image();
        var currentStep = this.currentStep;

        image.onload = this._onImageLoad(currentStep, image);
        image.onerror = this._onImageError(currentStep);
        image.src = this.imageURI + '/' + this.currentCountry.toLowerCase() +
          '_' + this.currentYear + '_' +
          NumbersHelper.padNumberToTwo(this.currentStep) + '.png';
      } else {
        var img = this.images[this.currentStep];

        if (img && img.image) {
          this._renderLegendImage(img.image);
        } else {
          this._renderLegendImage(false);
        }
      }
    },

    /**
     * On image error
     * @param  {Number} current step
     */
    _onImageError: function(currentStep) {
      return (function(step) {
        return function(e) {
          this.images[step] = {
            loaded: false,
            image: null
          };

          if (step === this.currentStep) {
            this._renderLegendImage(false);
          }
        }.bind(this);
      }.bind(this))(currentStep);
    },

    /**
     * Updates the tooltip of the current step
     * @param  {Number} current step
     * @param  {Object} image object
     */
    _onImageLoad: function(currentStep, image) {
      return (function(step, image) {
        return function(e) {
          this.images[step] = {
            loaded: true,
            image: image
          };

          if (step === this.currentStep) {
            this._renderLegendImage(image);
          }
        }.bind(this);
      }.bind(this))(currentStep, image);
    },

    /**
     * Renders the image or the error message
     * @param  {Object} image object
     */
    _renderLegendImage: function(image) {
      this.legendImage.innerHTML = '';
      this.legendImage.classList.remove('-no-data');

      if (image) {
        this.legendImage.appendChild(image);
      } else {
        this.legendImage.innerHTML = 'Not available';
        this.legendImage.classList.add('-no-data');
      }
    },
  });

  return InsightsGladAlerts;

});
<commit_msg>Fix: Insights - Glad - year selector and reset of the stored images<commit_after>define([
  'backbone',
  'handlebars',
  'd3',
  'underscore',
  '_string',
  'insights/views/InsightsGladAlertsChartView',
  'views/ShareView',
  'views/SourceModalView',
  'helpers/NumbersHelper',
  'text!insights/templates/insights-glad-alerts.handlebars',
  'text!insights/templates/insights-glad-alerts-legend.handlebars',
], function(Backbone, Handlebars, d3, _, _string, InsightsGladAlertsChart,
  ShareView, SourceModalView, NumbersHelper, tpl, tplLegend) {

  'use strict';

  var API = window.gfw.config.GFW_API_HOST_V2;
  var ENDPOINT_CONFIG = '/query/b0c709f0-d1a6-42a0-a750-df8bdb0895f3?sql=SELECT * FROM data';
  var ENDPOINT_DATA = '/query/9ed18255-89a9-4ccd-bdd6-fe7ffa1b1595?sql=SELECT sum(alerts::int) AS alerts, sum(cumulative_emissions::float) AS cumulative_emissions, sum(cumulative_emissions::float) AS cumulative_emissions, sum(above_ground_carbon_loss::float) AS above_ground_carbon_loss, sum(percent_to_emissions_target::float) AS percent_to_emissions_target, sum(percent_to_deforestation_target::float) AS percent_to_deforestation_target, sum(loss::float) AS loss, sum(cumulative_deforestation::float) AS cumulative_deforestation, year::text as year, country_iso, week FROM data WHERE ((country_iso IN (\'%s\') OR state_iso IN (\'%s\')) AND year IN (\'%s\') AND week::int < 52) GROUP BY year, country_iso, week ORDER BY week::int ASC';

  var WEEKS_YEAR = 52;

  var InsightsGladAlerts = Backbone.View.extend({

    events: {
      'click .js-selector': '_changeVisualizations',
      'click .js-share': '_openShare',
      'click .js-year-nav': '_changeYear',
      'change .js-country-selector': '_changeDataByCountry',
      'change .js-year-selector': '_changeDataByYear'
    },

    el: '#insights',

    template: Handlebars.compile(tpl),

    templateLegend: Handlebars.compile(tplLegend),

    defaults: {
      selectedClassEl: '-selected',
      filter: 'carbon_emissions',
      country: 'BRA',
      year: 2016,
      weekStep: 1,
      desforestationFilter: 'deforestation',
      loadingClassEl: 'is-loading',
      mainVisSwitchEl: 'main-vis-switch',
      loadedClassEl: 'loaded',
      countryLabelClassEl: 'js-country-label',
      countrySelectorClassEl: 'js-country-selector',
      legendSelectoClassEl: 'js-legend',
      noDataClassEl: 'no-data',
      imageURI: window.gfw.config.GFW_DATA_S3 + 'climate/glad_maps'
    },

    initialize: function() {
      this.legends = [];
      this.chartConfig = [];
      this.locations = [];
      this.images = {};
      this.currentStep = this.defaults.weekStep;
      this.currentCountry = this.defaults.country;
      this.currentYear = this.defaults.year;
      this.filter = this.defaults.filter;
      this.imageURI = this.defaults.imageURI;

      // Get the visualization's configuration
      $.when(this._getConfig())
        .done(this._initVisualization.bind(this));
    },

    _getConfig: function() {
      return $.ajax({
        url: API + ENDPOINT_CONFIG,
        type: 'GET'
      });
    },

    _initVisualization: function(config) {
      this.config = this._parseConfig(config);
      this.render();
      this._setListeners();

      // Info window
      new SourceModalView();
    },

    _parseConfig: function(config) {
      var data = config && config.data &&
      config.data[0] ? config.data[0] : {};

      if (data) {
        this.chartConfig = JSON.parse(data.vizsetup);
        this.locations = JSON.parse(data.locations);
      }
    },

    render: function() {
      this.$el.html(this.template({
        locations: this.locations
      }));
      this.$el.removeClass(this.defaults.loadingClassEl);
      this.$el.addClass(this.defaults.loadedClassEl);

      // Set default selection from the config
      var defaultCountry = _.findWhere(this.locations, {
        iso: this.chartConfig.defaultSelection
      });

      if (defaultCountry) {
        this._setCurrentCountry(defaultCountry.name, defaultCountry.iso);
      }

      this._renderMainChart();
    },

    _setListeners: function() {
      Backbone.Events.on('insights:glad:update', this._updateLegends.bind(this));
    },

    _renderMainChart: function() {
      var $el = this.el.querySelector('#visMain');
      var $vis = $el.querySelector('.visualization');
      var $chartEl = $el.querySelector('.chart');
      var iso = this.currentCountry;
      var year = this.currentYear;

      this._clearVisualization();

      $chartEl.innerHTML = '';
      $vis.classList.add(this.defaults.loadingClassEl);

      $.ajax({
        url: API + _.str.sprintf(ENDPOINT_DATA, iso, iso, year),
        type: 'GET',
        success: function(res) {
          var data = res.data;

          if (data.length) {
            $el.classList.remove(this.defaults.noDataClassEl);
            this._createVisualization(data);
          } else {
            $el.classList.add(this.defaults.noDataClassEl);
            this._renderNoDataPlaceHolder();
          }

          $vis.classList.remove(this.defaults.loadingClassEl);
        }.bind(this)
      });
    },

    _createVisualization: function(data) {
      var el = this.el.querySelector('#visMain');
      var chartEl = el.querySelector('.chart');
      var legendEl = el.querySelector('.legend');

      this._setMaxWeek(data);

      this.visMain = new InsightsGladAlertsChart({
        el: chartEl,
        params: {
          data: this._parseData(data),
          filter: this.filter,
          currentStep: this.currentStep,
          iso: this.currentCountry,
          year: this.currentYear,
          desforestationFilter: this.defaults.desforestationFilter,
        }
      });

      el.classList.remove(this.defaults.loadingClassEl);
      this._createLegend(legendEl, data, 'main');
    },

    _renderNoDataPlaceHolder: function() {
      var el = this.el.querySelector('#visMain');
      var chartEl = el.querySelector('.chart');

      chartEl.innerHTML = 'There\'s no data available for this selection';
    },

    _parseData: function(data) {
      return _.map(data, function(d) {
        var locationData = _.findWhere(this.locations, {
          iso: this.currentCountry
        });

        if (locationData) {
          d.carbon_average = locationData.targets['carbon_emissions'].average;
          d.carbon_target = locationData.targets['carbon_emissions'].target;
          d.deforestation_average = locationData.targets['deforestation'].average;
          d.deforestation_target = locationData.targets['deforestation'].target;
        }

        if (d.iso === this.chartConfig.defaultSelection) {
          d.selected = true;
        }

        return d;
      }.bind(this));
    },

    _setMaxWeek: function(data) {
      var lastValue = data && data[data.length - 1] ?
        data[data.length - 1] : [];

      if (lastValue) {
        this.currentStep = (lastValue.week * 1);
      }
    },

    _getIso: function() {
      var country = this.currentCountry;
      var re = /(\d+)/g;
      var subst = '-$1';

      country = country.replace(re, subst);
      return country;
    },

    _createLegend: function(el, data, category) {
      this.legends.push({
        element: el,
        data: data,
        category: category,
        filter: this.filter
      });

      this._renderLegend(el, data, this.filter);
    },

    _renderLegend: function(el, data, filter) {
      var current = _.filter(data, function(d) {
        return (d.week * 1) === this.currentStep;
      }.bind(this))[0];

      if (current) {
        var target = Math.round(current.carbon_target * 1);
        var emissions = Math.round(current.cumulative_emissions * 1);
        var deforestation = Math.round(current.cumulative_deforestation * 1);
        var target_deforestation = Math.round(current.deforestation_target * 1);
        var alerts = Math.round(current.alerts);
        var annual_budget = Math.round(((emissions / target) * 100));
        var annual_budget_deforestation = Math.round(((deforestation / target_deforestation) * 100));
        var co2Equivalency = Math.round(((current.above_ground_carbon_loss * 1) * 10000000) / 4.7);

        var date = moment.utc().year(this.currentYear);
        var weeksInYear = date.weeksInYear();
        var currentWeek = weeksInYear > WEEKS_YEAR ? (this.currentStep + 1) : this.currentStep;
        var dateWithWeek = date.week(currentWeek);

        if (weeksInYear > WEEKS_YEAR) {
          dateWithWeek.subtract(1, 'days');
        } else {
          dateWithWeek.add(1, 'days');
        }

        var begin = dateWithWeek.clone().format('YYYY-MM-DD');
        var end = dateWithWeek.clone().add(6, 'days').format('YYYY-MM-DD');

        el.innerHTML = this.templateLegend({
          isDesforestation: filter === this.defaults.desforestationFilter,
          annual_budget:  NumbersHelper.addNumberDecimals(annual_budget),
          annual_budget_deforestation: NumbersHelper.addNumberDecimals(annual_budget_deforestation),
          alerts: NumbersHelper.addNumberDecimals(alerts),
          co2Equivalency: NumbersHelper.addNumberDecimals(co2Equivalency),
          begin: begin,
          end: end,
          iso: this._getIso(),
          week: this.currentStep
        });

        this.legendImage = el.querySelector('.image');
        this.showImage();
      }
    },

    _updateLegends: function(state) {
      if (this.currentStep !== state.step) {
        this.currentStep = state.step;
        this.maxData = state.maxData;
        this.legends.forEach(function(legend) {
          this._renderLegend(legend.element, legend.data, legend.filter);
        }.bind(this));
      }
    },

    _changeVisualizations: function(ev) {
      var current = ev.currentTarget;
      var filter = current.dataset.filter;

      this._changeMainVis(filter);
    },

    _changeMainVis: function(filter) {
      this._toggleFilter(this.defaults.mainVisSwitchEl, filter);

      this.visMain.updateByFilter(filter);

      var legend = _.findWhere(this.legends, {
        category: 'main'
      });
      legend.filter = filter;

      this._renderLegend(legend.element, legend.data, legend.filter);
    },

    _clearVisualization: function() {
      if (this.visMain) {
        this.visMain.remove();
        this.legends = [];
        this.images = {}

        var legend = this.el.querySelector('.' + this.defaults.legendSelectoClassEl);
        legend.innerHTML = '';
      }
    },

    _changeDataByCountry: function(ev) {
      var current = ev.currentTarget;
      var value = current.value;
      var selected = current.querySelector('[data-iso="'+ value +'"]');

      this._setCurrentCountry(selected.text, value);
      this._renderMainChart();
    },

    _setCurrentCountry: function(text, iso) {
      var label = this.el.querySelector('.' + this.defaults.countryLabelClassEl);
      var selector = this.el.querySelector('.' + this.defaults.countrySelectorClassEl);
      label.innerHTML = text;
      selector.value = iso;

      this.currentCountry = iso;
    },

    _changeDataByYear: function(ev) {
      var current = ev.currentTarget;
      this.currentYear = parseInt(current.value, 10);

      this._renderMainChart();
    },

    _changeYear: function(ev) {
      var action = ev.currentTarget.dataset.action;
      var $selector = ev.currentTarget.parentNode.querySelector('.js-year-selector');
      var options = $selector.options;
      var current = $selector.selectedIndex;
      var max = $selector.length - 1;
      var newIndex = current;

      if (action === 'add') {
        newIndex++;
      } else if (action === 'sub') {
        newIndex--;
      }

      if (newIndex < 0) {
        newIndex = max;
      } else if (newIndex > max) {
        newIndex = 0;
      }
      $selector.selectedIndex = newIndex;
      this.currentYear = parseInt(options[newIndex].text, 10);

      this._renderMainChart();
    },

    _toggleFilter: function(element, filter) {
      var parent = this.el.querySelector('.' + element);
      var selected = parent.querySelector('.' + this.defaults.selectedClassEl);
      var newSelection = parent.querySelector('[data-filter="'+ filter +'"]');

      selected.classList.remove(this.defaults.selectedClassEl);
      newSelection.classList.add(this.defaults.selectedClassEl);
      this.filter = filter;
    },

    _openShare: function(event) {
      var shareView = new ShareView().share(event);
      $('body').append(shareView.el);
    },

    /**
     * Shows the image of the current step
     */
    showImage: function() {
      var maxData = this.maxData || this.currentStep;

      if (!this.images[this.currentStep] &&
        (this.currentStep <= maxData)) {
        var image = new Image();
        var currentStep = this.currentStep;

        image.onload = this._onImageLoad(currentStep, image);
        image.onerror = this._onImageError(currentStep);
        image.src = this.imageURI + '/' + this.currentCountry.toLowerCase() +
          '_' + this.currentYear + '_' +
          NumbersHelper.padNumberToTwo(this.currentStep) + '.png';
      } else {
        var img = this.images[this.currentStep];

        if (img && img.image) {
          this._renderLegendImage(img.image);
        } else {
          this._renderLegendImage(false);
        }
      }
    },

    /**
     * On image error
     * @param  {Number} current step
     */
    _onImageError: function(currentStep) {
      return (function(step) {
        return function(e) {
          this.images[step] = {
            loaded: false,
            image: null
          };

          if (step === this.currentStep) {
            this._renderLegendImage(false);
          }
        }.bind(this);
      }.bind(this))(currentStep);
    },

    /**
     * Updates the tooltip of the current step
     * @param  {Number} current step
     * @param  {Object} image object
     */
    _onImageLoad: function(currentStep, image) {
      return (function(step, image) {
        return function(e) {
          this.images[step] = {
            loaded: true,
            image: image
          };

          if (step === this.currentStep) {
            this._renderLegendImage(image);
          }
        }.bind(this);
      }.bind(this))(currentStep, image);
    },

    /**
     * Renders the image or the error message
     * @param  {Object} image object
     */
    _renderLegendImage: function(image) {
      this.legendImage.innerHTML = '';
      this.legendImage.classList.remove('-no-data');

      if (image) {
        this.legendImage.appendChild(image);
      } else {
        this.legendImage.innerHTML = 'Not available';
        this.legendImage.classList.add('-no-data');
      }
    },
  });

  return InsightsGladAlerts;

});
<|endoftext|>


<commit_before>var Entity = require('./entity')

module.exports.AddressSchema = new Entity.SchemaObject({
    AddressLine1: { type: String, toObject: 'always' },
    AddressLine2: { type: String, toObject: 'always' },
    AddressLine3: { type: String, toObject: 'always' },
    AddressLine4: { type: String, toObject: 'always' },
    City: { type: String, toObject: 'always' },
    Region: { type: String, toObject: 'always' },
    PostalCode: { type: String, toObject: 'always' },
    Country: { type: String, toObject: 'always' },
    AttentionTo: { type: String, toObject: 'always' },
    AddressType: { type: String, toObject: 'always' }
});

module.exports.PhoneSchema = new Entity.SchemaObject({
    PhoneNumber: { type: String, toObject: 'always' },
    PhoneAreaCode: { type: String, toObject: 'always' },
    PhoneType: { type: String, toObject: 'always' }
});

module.exports.ExternalLinkSchema = new Entity.SchemaObject({
    LinkType: { type: String },
    Url: { type: String }
});

module.exports.ContactPerson = new Entity.SchemaObject({
    FirstName: { type: String },
    LastName: { type: String },
    EmailAddress: { type: String },
    IncludeInEmails: { type: Boolean }
});

module.exports.PaymentTermSchema = new Entity.SchemaObject({
    Day: { type: Number },
    Type: { type: String }
});

module.exports.BrandingScheme = new Entity.SchemaObject({
    BrandingThemeID: { type: String },
    Name: { type: String },
    SortOrder: { type: Number },
    CreatedDateUTC: { type: Date }
});

module.exports.LineItemSchema = new Entity.SchemaObject({
    Description: { type: String },
    Quantity: { type: Number },
    UnitAmount: { type: Number },
    AccountCode: { type: String },
    ItemCode: { type: String },
    TaxType: { type: String },
    LineAmount: { type: Number },
    Tracking: { type: String }
});

var InvoiceIDSchema = new Entity.SchemaObject({
    InvoiceID: { type: String, toObject: 'hasValue' },
    InvoiceNumber: { type: String, toObject: 'hasValue' }
});

var CreditNoteIDSchema = new Entity.SchemaObject({
    CreditNoteID: { type: String, toObject: 'hasValue' },
    CreditNoteNumber: { type: String, toObject: 'hasValue' }
});

var AccountIDorCodeSchema = new Entity.SchemaObject({
    AccountID: { type: String, toObject: 'hasValue' },
    Code: { type: String, toObject: 'hasValue' }
});

module.exports.PaymentSchema = new Entity.SchemaObject({
    Invoice: { type: InvoiceIDSchema, toObject: 'always' },
    CreditNote: { type: CreditNoteIDSchema, toObject: 'always' },
    Account: { type: AccountIDorCodeSchema, toObject: 'always' },
    Date: { type: String, toObject: 'always' },
    CurrencyRate: { type: Number, toObject: 'hasValue' },
    Amount: { type: Number, toObject: 'hasValue' },
    Reference: { type: String, toObject: 'hasValue' },
    IsReconciled: { type: Boolean, toObject: 'hasValue' },
    Status: { type: String, toObject: 'hasValue' },
    PaymentType: { type: String, toObject: 'hasValue' },
    UpdatedDateUTC: { type: String, toObject: 'hasValue' }
});

module.exports.TrackingOptionSchema = new Entity.SchemaObject({
    TrackingOptionID: { type: String, toObject: 'always' },
    Name: { type: String, toObject: 'always' },
    Status: { type: String, toObject: 'always' }
});

var TrackingCategoryOptionsSchema = new Entity.SchemaObject({
    TrackingCategoryID: { type: String, toObject: 'hasValue' },
    Name: { type: String, toObject: 'hasValue' },
    Option: { type: String, toObject: 'hasValue' }
});

var TrackingCategorySchema = new Entity.SchemaObject({
    TrackingCategory: { type: TrackingCategoryOptionsSchema, toObject: 'hasValue' }
});

module.exports.LineItemSchema = new Entity.SchemaObject({
    Description: { type: String },
    Quantity: { type: Number },
    UnitAmount: { type: Number },
    ItemCode: { type: String },
    AccountCode: { type: String },
    TaxType: { type: String },
    TaxAmount: { type: Number },
    LineAmount: { type: Number },
    Tracking: { type: Array, arrayType: TrackingCategorySchema, toObject: 'hasValue' },
    DiscountRate: { type: Number }
});<commit_msg>Remove arbitrary LineItemSchema from shared.js<commit_after>var Entity = require('./entity')

module.exports.AddressSchema = new Entity.SchemaObject({
    AddressLine1: { type: String, toObject: 'always' },
    AddressLine2: { type: String, toObject: 'always' },
    AddressLine3: { type: String, toObject: 'always' },
    AddressLine4: { type: String, toObject: 'always' },
    City: { type: String, toObject: 'always' },
    Region: { type: String, toObject: 'always' },
    PostalCode: { type: String, toObject: 'always' },
    Country: { type: String, toObject: 'always' },
    AttentionTo: { type: String, toObject: 'always' },
    AddressType: { type: String, toObject: 'always' }
});

module.exports.PhoneSchema = new Entity.SchemaObject({
    PhoneNumber: { type: String, toObject: 'always' },
    PhoneAreaCode: { type: String, toObject: 'always' },
    PhoneType: { type: String, toObject: 'always' }
});

module.exports.ExternalLinkSchema = new Entity.SchemaObject({
    LinkType: { type: String },
    Url: { type: String }
});

module.exports.ContactPerson = new Entity.SchemaObject({
    FirstName: { type: String },
    LastName: { type: String },
    EmailAddress: { type: String },
    IncludeInEmails: { type: Boolean }
});

module.exports.PaymentTermSchema = new Entity.SchemaObject({
    Day: { type: Number },
    Type: { type: String }
});

module.exports.BrandingScheme = new Entity.SchemaObject({
    BrandingThemeID: { type: String },
    Name: { type: String },
    SortOrder: { type: Number },
    CreatedDateUTC: { type: Date }
});

var InvoiceIDSchema = new Entity.SchemaObject({
    InvoiceID: { type: String, toObject: 'hasValue' },
    InvoiceNumber: { type: String, toObject: 'hasValue' }
});

var CreditNoteIDSchema = new Entity.SchemaObject({
    CreditNoteID: { type: String, toObject: 'hasValue' },
    CreditNoteNumber: { type: String, toObject: 'hasValue' }
});

var AccountIDorCodeSchema = new Entity.SchemaObject({
    AccountID: { type: String, toObject: 'hasValue' },
    Code: { type: String, toObject: 'hasValue' }
});

module.exports.PaymentSchema = new Entity.SchemaObject({
    Invoice: { type: InvoiceIDSchema, toObject: 'always' },
    CreditNote: { type: CreditNoteIDSchema, toObject: 'always' },
    Account: { type: AccountIDorCodeSchema, toObject: 'always' },
    Date: { type: String, toObject: 'always' },
    CurrencyRate: { type: Number, toObject: 'hasValue' },
    Amount: { type: Number, toObject: 'hasValue' },
    Reference: { type: String, toObject: 'hasValue' },
    IsReconciled: { type: Boolean, toObject: 'hasValue' },
    Status: { type: String, toObject: 'hasValue' },
    PaymentType: { type: String, toObject: 'hasValue' },
    UpdatedDateUTC: { type: String, toObject: 'hasValue' }
});

module.exports.TrackingOptionSchema = new Entity.SchemaObject({
    TrackingOptionID: { type: String, toObject: 'always' },
    Name: { type: String, toObject: 'always' },
    Status: { type: String, toObject: 'always' }
});

var TrackingCategoryOptionsSchema = new Entity.SchemaObject({
    TrackingCategoryID: { type: String, toObject: 'hasValue' },
    Name: { type: String, toObject: 'hasValue' },
    Option: { type: String, toObject: 'hasValue' }
});

var TrackingCategorySchema = new Entity.SchemaObject({
    TrackingCategory: { type: TrackingCategoryOptionsSchema, toObject: 'hasValue' }
});

module.exports.LineItemSchema = new Entity.SchemaObject({
    Description: { type: String },
    Quantity: { type: Number },
    UnitAmount: { type: Number },
    ItemCode: { type: String },
    AccountCode: { type: String },
    TaxType: { type: String },
    TaxAmount: { type: Number },
    LineAmount: { type: Number },
    Tracking: { type: Array, arrayType: TrackingCategorySchema, toObject: 'hasValue' },
    DiscountRate: { type: Number }
});<|endoftext|>


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

var util = require('../util');
var bignum = util.bignum;
var common = require('./common');
var LobOptions = common.LobOptions;

module.exports = Reader;

function Reader(buffer, lobFactory, scrictEncoding) {
  this.buffer = buffer;
  this.offset = 0;
  this.lobFactory = lobFactory;
  this.scrictEncoding = scrictEncoding;
}

Reader.prototype.hasMore = function hasMore() {
  return this.offset < this.buffer.length;
};

Reader.prototype.readTinyInt = function readTinyInt() {
  if (this.buffer[this.offset++] === 0x00) {
    return null;
  }
  var value = this.buffer.readUInt8(this.offset, true);
  this.offset += 1;
  return value;
};

Reader.prototype.readSmallInt = function readSmallInt() {
  if (this.buffer[this.offset++] === 0x00) {
    return null;
  }
  var value = this.buffer.readInt16LE(this.offset, true);
  this.offset += 2;
  return value;
};

Reader.prototype.readInt = function readInt() {
  if (this.buffer[this.offset++] === 0x00) {
    return null;
  }
  var value = this.buffer.readInt32LE(this.offset, true);
  this.offset += 4;
  return value;
};

Reader.prototype.readBigInt = function readBigInt() {
  if (this.buffer[this.offset++] === 0x00) {
    return null;
  }
  var value = bignum.readInt64LE(this.buffer, this.offset);
  this.offset += 8;
  return value;
};

Reader.prototype.readString = function readString() {
  return this.readBytes(true);
};

Reader.prototype.readBinary = function readBinary() {
  return this.readBytes();
};

Reader.prototype.readBytes = function readBytes(isString) {
  var length = this.buffer[this.offset++];
  switch (length) {
    case 0xff:
      return null;
    case 0xf6:
      length = this.buffer.readInt16LE(this.offset, true);
      this.offset += 2;
      break;
    case 0xf7:
      length = this.buffer.readInt32LE(this.offset, true);
      this.offset += 4;
      break;
    default:
    // do nothing
  }
  var value;
  if (isString) {
    value = util.convert.decode(
      this.buffer.slice(this.offset, this.offset + length),
      this.scrictEncoding);
  } else {
    value = new Buffer(length);
    this.buffer.copy(value, 0, this.offset, this.offset + length);
  }
  this.offset += length;
  return value;
};

Reader.prototype.readDate = function readDate() {
  /* jshint bitwise:false */
  var high = this.buffer[this.offset + 1];
  // msb not set ==> null
  if (!(high & 0x80)) {
    this.offset += 4;
    return null;
  }
  var year = this.buffer[this.offset];
  this.offset += 2;
  var month = this.buffer[this.offset] + 1;
  this.offset += 1;
  var day = this.buffer[this.offset];
  this.offset += 1;
  // msb set ==> not null
  // unset msb and second most sb
  high &= 0x3f;
  year |= high << 8;
  return util.lpad4(year) + '-' +
    util.lpad2(month) + '-' +
    util.lpad2(day);
};

Reader.prototype.readTime = function readTime() {
  /* jshint bitwise:false */
  var hour = this.buffer[this.offset];
  // msb not set ==> null
  if (!(hour & 0x80)) {
    this.offset += 4;
    return null;
  }
  var min = this.buffer[this.offset + 1];
  this.offset += 2;
  var msec = this.buffer.readUInt16LE(this.offset, true);
  this.offset += 2;
  // msb set ==> not null
  // unset msb
  hour &= 0x7f;
  return util.lpad2(hour) + ':' +
    util.lpad2(min) + ':' +
    util.lpad2(msec / 1000);
};

Reader.prototype.readTimestamp = function readTimestamp() {
  var date = this.readDate();
  var time = this.readTime();
  if (date) {
    if (time) {
      return date + 'T' + time;
    }
    return date + 'T00:00:00';
  }
  if (time) {
    return '0001-01-01T' + time;
  }
  return null;
};

Reader.prototype.readDayDate = function readDayDate() {
  var value = this.buffer.readInt32LE(this.offset, true);
  this.offset += 4;
  if (value === 3652062 || value === 0) {
    return null;
  }
  return value - 1;
};

Reader.prototype.readSecondTime = function readSecondTime() {
  var value = this.buffer.readInt32LE(this.offset, true);
  this.offset += 4;
  if (value === 86402 || value === 0) {
    return null;
  }
  return value - 1;
};

Reader.prototype.readSecondDate = function readSecondDate() {
  var value = bignum.readInt64LE(this.buffer, this.offset);
  this.offset += 8;
  if (value === 315538070401 || value === 0) {
    return null;
  }
  return value - 1;
};

Reader.prototype.readLongDate = function readLongDate() {
  var value = bignum.readInt64LE(this.buffer, this.offset);
  this.offset += 8;
  if (value === '3155380704000000001' || value === 0) {
    return null;
  }
  if (typeof value === 'string') {
    /* FIXME */
    var index = value.length - 7;
    var secondDate = parseInt(value.substring(0, index), 10);
    var fractionalSeconds = parseInt(value.substring(index), 10) - 1;
    if (fractionalSeconds < 0) {
      fractionalSeconds = 9999999;
      secondDate -= 1;
    }
    return secondDate + util.lpad7(fractionalSeconds);
  }
  return value - 1;
};

Reader.prototype.readBLob = function readBLob() {
  return this.readLob(1);
};

Reader.prototype.readCLob = function readCLob() {
  return this.readLob(2);
};

Reader.prototype.readNCLob = function readNCLob() {
  return this.readLob(3);
};

Reader.prototype.readLob = function readLob(type) {
  /* jshint bitwise:false, unused:false */
  // offset 0
  type = this.buffer[this.offset] || type;
  this.offset += 1;
  // offset 1
  var options = this.buffer[this.offset];
  this.offset += 1;
  // offset 2
  if (options & LobOptions.NULL_INDICATOR) {
    return null;
  }
  // skip 2 byte filler
  this.offset += 2;
  // offset 4
  var charLength = bignum.readInt64LE(this.buffer, this.offset);
  this.offset += 8;
  // offset 12
  var byteLength = bignum.readInt64LE(this.buffer, this.offset);
  this.offset += 8;
  // offset 20
  var locatorId = this.buffer.slice(this.offset, this.offset + 8);
  this.offset += 8;
  // offset 28
  var chunkLength = this.buffer.readInt32LE(this.offset, true);
  this.offset += 4;
  // offset 32
  var chunk = null;
  if (chunkLength > 0) {
    chunk = this.buffer.slice(this.offset, this.offset + chunkLength);
    this.offset += chunkLength;
  } else {
    chunk = new Buffer(0);
  }
  // if (!byteLength && !charLength) { return null; }
  var ld = new LobDescriptor(type, options, charLength, byteLength, locatorId, chunk);
  return this.lobFactory.createLob(ld);
};

Reader.prototype.readDouble = function readDouble() {
  if (this.buffer[this.offset] === 0xff &&
    this.buffer[this.offset + 1] === 0xff &&
    this.buffer[this.offset + 2] === 0xff &&
    this.buffer[this.offset + 3] === 0xff &&
    this.buffer[this.offset + 4] === 0xff &&
    this.buffer[this.offset + 5] === 0xff &&
    this.buffer[this.offset + 6] === 0xff &&
    this.buffer[this.offset + 7] === 0xff) {
    this.offset += 8;
    return null;
  }
  var value = this.buffer.readDoubleLE(this.offset, true);
  this.offset += 8;
  return value;
};

Reader.prototype.readFloat = function readFloat() {
  if (this.buffer[this.offset] === 0xff &&
    this.buffer[this.offset + 1] === 0xff &&
    this.buffer[this.offset + 2] === 0xff &&
    this.buffer[this.offset + 3] === 0xff) {
    this.offset += 4;
    return null;
  }
  var value = this.buffer.readFloatLE(this.offset, true);
  this.offset += 4;
  return value;
};

Reader.prototype.readDecimal = function readDecimal(fraction) {
  var value;
  if (fraction > 34) {
    value = bignum.readDecFloat(this.buffer, this.offset);
  } else {
    value = bignum.readDecFixed(this.buffer, this.offset, fraction);
  }
  this.offset += 16;
  return value;
};

function LobDescriptor(type, options, charLength, byteLength, locatorId, chunk) {
  this.type = type;
  this.options = options;
  this.charLength = charLength;
  this.byteLength = byteLength;
  this.locatorId = locatorId;
  this.chunk = chunk;
}

Object.defineProperties(LobDescriptor.prototype, {
  isLast: {
    get: function isLast() {
      /* jshint bitwise:false */
      return !!(this.options & LobOptions.LAST_DATA);
    }
  }
});<commit_msg>Remove buffers `noAssert` argument (#125)<commit_after>// Copyright 2013 SAP AG.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http: //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific
// language governing permissions and limitations under the License.
'use strict';

var util = require('../util');
var bignum = util.bignum;
var common = require('./common');
var LobOptions = common.LobOptions;

module.exports = Reader;

function Reader(buffer, lobFactory, scrictEncoding) {
  this.buffer = buffer;
  this.offset = 0;
  this.lobFactory = lobFactory;
  this.scrictEncoding = scrictEncoding;
}

Reader.prototype.hasMore = function hasMore() {
  return this.offset < this.buffer.length;
};

Reader.prototype.readTinyInt = function readTinyInt() {
  if (this.buffer[this.offset++] === 0x00) {
    return null;
  }
  var value = this.buffer.readUInt8(this.offset);
  this.offset += 1;
  return value;
};

Reader.prototype.readSmallInt = function readSmallInt() {
  if (this.buffer[this.offset++] === 0x00) {
    return null;
  }
  var value = this.buffer.readInt16LE(this.offset);
  this.offset += 2;
  return value;
};

Reader.prototype.readInt = function readInt() {
  if (this.buffer[this.offset++] === 0x00) {
    return null;
  }
  var value = this.buffer.readInt32LE(this.offset);
  this.offset += 4;
  return value;
};

Reader.prototype.readBigInt = function readBigInt() {
  if (this.buffer[this.offset++] === 0x00) {
    return null;
  }
  var value = bignum.readInt64LE(this.buffer, this.offset);
  this.offset += 8;
  return value;
};

Reader.prototype.readString = function readString() {
  return this.readBytes(true);
};

Reader.prototype.readBinary = function readBinary() {
  return this.readBytes();
};

Reader.prototype.readBytes = function readBytes(isString) {
  var length = this.buffer[this.offset++];
  switch (length) {
    case 0xff:
      return null;
    case 0xf6:
      length = this.buffer.readInt16LE(this.offset);
      this.offset += 2;
      break;
    case 0xf7:
      length = this.buffer.readInt32LE(this.offset);
      this.offset += 4;
      break;
    default:
    // do nothing
  }
  var value;
  if (isString) {
    value = util.convert.decode(
      this.buffer.slice(this.offset, this.offset + length),
      this.scrictEncoding);
  } else {
    value = new Buffer(length);
    this.buffer.copy(value, 0, this.offset, this.offset + length);
  }
  this.offset += length;
  return value;
};

Reader.prototype.readDate = function readDate() {
  /* jshint bitwise:false */
  var high = this.buffer[this.offset + 1];
  // msb not set ==> null
  if (!(high & 0x80)) {
    this.offset += 4;
    return null;
  }
  var year = this.buffer[this.offset];
  this.offset += 2;
  var month = this.buffer[this.offset] + 1;
  this.offset += 1;
  var day = this.buffer[this.offset];
  this.offset += 1;
  // msb set ==> not null
  // unset msb and second most sb
  high &= 0x3f;
  year |= high << 8;
  return util.lpad4(year) + '-' +
    util.lpad2(month) + '-' +
    util.lpad2(day);
};

Reader.prototype.readTime = function readTime() {
  /* jshint bitwise:false */
  var hour = this.buffer[this.offset];
  // msb not set ==> null
  if (!(hour & 0x80)) {
    this.offset += 4;
    return null;
  }
  var min = this.buffer[this.offset + 1];
  this.offset += 2;
  var msec = this.buffer.readUInt16LE(this.offset);
  this.offset += 2;
  // msb set ==> not null
  // unset msb
  hour &= 0x7f;
  return util.lpad2(hour) + ':' +
    util.lpad2(min) + ':' +
    util.lpad2(msec / 1000);
};

Reader.prototype.readTimestamp = function readTimestamp() {
  var date = this.readDate();
  var time = this.readTime();
  if (date) {
    if (time) {
      return date + 'T' + time;
    }
    return date + 'T00:00:00';
  }
  if (time) {
    return '0001-01-01T' + time;
  }
  return null;
};

Reader.prototype.readDayDate = function readDayDate() {
  var value = this.buffer.readInt32LE(this.offset);
  this.offset += 4;
  if (value === 3652062 || value === 0) {
    return null;
  }
  return value - 1;
};

Reader.prototype.readSecondTime = function readSecondTime() {
  var value = this.buffer.readInt32LE(this.offset);
  this.offset += 4;
  if (value === 86402 || value === 0) {
    return null;
  }
  return value - 1;
};

Reader.prototype.readSecondDate = function readSecondDate() {
  var value = bignum.readInt64LE(this.buffer, this.offset);
  this.offset += 8;
  if (value === 315538070401 || value === 0) {
    return null;
  }
  return value - 1;
};

Reader.prototype.readLongDate = function readLongDate() {
  var value = bignum.readInt64LE(this.buffer, this.offset);
  this.offset += 8;
  if (value === '3155380704000000001' || value === 0) {
    return null;
  }
  if (typeof value === 'string') {
    /* FIXME */
    var index = value.length - 7;
    var secondDate = parseInt(value.substring(0, index), 10);
    var fractionalSeconds = parseInt(value.substring(index), 10) - 1;
    if (fractionalSeconds < 0) {
      fractionalSeconds = 9999999;
      secondDate -= 1;
    }
    return secondDate + util.lpad7(fractionalSeconds);
  }
  return value - 1;
};

Reader.prototype.readBLob = function readBLob() {
  return this.readLob(1);
};

Reader.prototype.readCLob = function readCLob() {
  return this.readLob(2);
};

Reader.prototype.readNCLob = function readNCLob() {
  return this.readLob(3);
};

Reader.prototype.readLob = function readLob(type) {
  /* jshint bitwise:false, unused:false */
  // offset 0
  type = this.buffer[this.offset] || type;
  this.offset += 1;
  // offset 1
  var options = this.buffer[this.offset];
  this.offset += 1;
  // offset 2
  if (options & LobOptions.NULL_INDICATOR) {
    return null;
  }
  // skip 2 byte filler
  this.offset += 2;
  // offset 4
  var charLength = bignum.readInt64LE(this.buffer, this.offset);
  this.offset += 8;
  // offset 12
  var byteLength = bignum.readInt64LE(this.buffer, this.offset);
  this.offset += 8;
  // offset 20
  var locatorId = this.buffer.slice(this.offset, this.offset + 8);
  this.offset += 8;
  // offset 28
  var chunkLength = this.buffer.readInt32LE(this.offset);
  this.offset += 4;
  // offset 32
  var chunk = null;
  if (chunkLength > 0) {
    chunk = this.buffer.slice(this.offset, this.offset + chunkLength);
    this.offset += chunkLength;
  } else {
    chunk = new Buffer(0);
  }
  // if (!byteLength && !charLength) { return null; }
  var ld = new LobDescriptor(type, options, charLength, byteLength, locatorId, chunk);
  return this.lobFactory.createLob(ld);
};

Reader.prototype.readDouble = function readDouble() {
  if (this.buffer[this.offset] === 0xff &&
    this.buffer[this.offset + 1] === 0xff &&
    this.buffer[this.offset + 2] === 0xff &&
    this.buffer[this.offset + 3] === 0xff &&
    this.buffer[this.offset + 4] === 0xff &&
    this.buffer[this.offset + 5] === 0xff &&
    this.buffer[this.offset + 6] === 0xff &&
    this.buffer[this.offset + 7] === 0xff) {
    this.offset += 8;
    return null;
  }
  var value = this.buffer.readDoubleLE(this.offset);
  this.offset += 8;
  return value;
};

Reader.prototype.readFloat = function readFloat() {
  if (this.buffer[this.offset] === 0xff &&
    this.buffer[this.offset + 1] === 0xff &&
    this.buffer[this.offset + 2] === 0xff &&
    this.buffer[this.offset + 3] === 0xff) {
    this.offset += 4;
    return null;
  }
  var value = this.buffer.readFloatLE(this.offset);
  this.offset += 4;
  return value;
};

Reader.prototype.readDecimal = function readDecimal(fraction) {
  var value;
  if (fraction > 34) {
    value = bignum.readDecFloat(this.buffer, this.offset);
  } else {
    value = bignum.readDecFixed(this.buffer, this.offset, fraction);
  }
  this.offset += 16;
  return value;
};

function LobDescriptor(type, options, charLength, byteLength, locatorId, chunk) {
  this.type = type;
  this.options = options;
  this.charLength = charLength;
  this.byteLength = byteLength;
  this.locatorId = locatorId;
  this.chunk = chunk;
}

Object.defineProperties(LobDescriptor.prototype, {
  isLast: {
    get: function isLast() {
      /* jshint bitwise:false */
      return !!(this.options & LobOptions.LAST_DATA);
    }
  }
});<|endoftext|>