<commit_before>// FastCGI
//
// Copyright 2011 Iris Couch
//
//    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.

// Specification: http://www.fastcgi.com/drupal/node/22

require('defaultable').def(module,
  { 'log': console
  }, function(module, exports, DEFS) {

var net = require('net')
var URL = require('url')
var util = require('util')
var http = require('http')
var FCGI = require('fastcgi-parser')

var LOG = DEFS.log

module.exports = { 'httpd': httpd
                 }

// Connect to a FastCGI service and run an HTTP front-end sending all requests to it.
function httpd(port, host, socket_path, callback) {
  connect_fcgi(socket_path, 0, function(er, socket) {
    if(er)
      return callback(er)

    fcgi_get_values(socket, function(er, values) {
      if(er)
        return callback(er)

      LOG.info('FCGI values: %j', values)
      var server = http.createServer(fcgi_handler(port, host, socket, socket_path))
      server.listen(port, host)
      return callback(null)
    })
  })
}

function fcgi_get_values(socket, callback) {
  LOG.info('Get FastCGI values')
  socket.on('data', on_data)

  var values = [ ['FCGI_MAX_CONNS' , '']
               , ['FCGI_MAX_REQS'  , '']
               , ['FCGI_MPXS_CONNS', '']
               ]

  var writer = new FCGI.writer
  writer.encoding = 'binary'

  writer.writeHeader({ 'version' : FCGI.constants.version
                     , 'type'    : FCGI.constants.record.FCGI_GET_VALUES
                     , 'recordId': 0
                     , 'contentLength': FCGI.getParamLength(values)
                     , 'paddingLength': 0
                     })
  writer.writeParams(values)
  socket.write(writer.tobuffer())

  writer.writeHeader({ 'version' : FCGI.constants.version
                     , 'type'    : FCGI.constants.record.FCGI_GET_VALUES
                     , 'recordId': 0
                     , 'contentLength': 0
                     , 'paddingLength': 0
                     })
  socket.write(writer.tobuffer())

  LOG.info('Listening for FastCGI values')
  var fcgi_values = {}
  var timeout = setTimeout(got_all_values, 100)

  function on_data(data) {
    var parser = new FCGI.parser
    parser.encoding = 'utf8'
    parser.onRecord = on_record
    parser.onError  = on_error
    parser.execute(data)
  }

  function on_error(er) {
    LOG.error('Error getting FastCGI values: %s', er.message || er)
    parser.onRecord = parser.onError = function() {}
    callback(er)
  }

  function on_record(record) {
    var params = record.body.params || {}
      , keys = Object.keys(params)

    keys.forEach(function(key) {
      var num_value = +params[key]
      fcgi_values[key] = isNaN(num_value) ? params[key] : num_value
    })

    if(keys.length == 0)
      got_all_values()
  }

  function got_all_values() {
    clearTimeout(timeout)
    socket.removeListener('data', on_data)
    callback(null, fcgi_values)
  }
}

function fcgi_handler(port, server_addr, socket, socket_path) {
  var requests = {}
    , request_id = 0

  prep_socket()
  return on_request

  function on_request(req, res) {
    LOG.info('Request: %j', req.url)

    request_id += 1
    requests[request_id] = { 'req': req
                           , 'res':res
                           , 'stdout': []
                           , 'stderr': []
                           }

    req.url = URL.parse(req.url)
    var cgi = { 'PATH_INFO': req.url.pathname
              , 'SERVER_NAME': server_addr || 'unknown'
              , 'SERVER_PORT': port
              , 'SERVER_PROTOCOL': 'HTTP/1.1'
              , 'SERVER_SOFTWARE': 'Node/' + process.version
              }

    Object.keys(req.headers).forEach(function(header) {
      var key = 'HTTP_' + header.toUpperCase().replace(/-/g, '_')
      cgi[key] = req.headers[header]
    })

    cgi.REQUEST_METHOD = req.method
    cgi.QUERY_STRING = req.url.query || ''
    if('content-length' in req.headers)
      cgi.CONTENT_LENGTH = req.headers['content-length']
    if('content-type' in req.headers)
      cgi.CONTENT_LENGTH = req.headers['content-type']
    if('authorization' in req.headers)
      cgi.AUTH_TYPE = req.headers.authorization.split(/ /)[0]

    //LOG.info('CGI: %j', cgi)

    var params = []
    Object.keys(cgi).forEach(function(key) {
      params.push([key, cgi[key]])
    })

    // Write the request to FastCGI.
    var writer = new FCGI.writer
    writer.encoding = 'binary'

    // Begin
    writer.writeHeader({ 'version' : FCGI.constants.version
                       , 'type'    : FCGI.constants.record.FCGI_BEGIN
                       , 'recordId': request_id
                       , 'contentLength': 8
                       , 'paddingLength': 0
                       })
    writer.writeBegin({ 'role': FCGI.constants.role.FCGI_RESPONDER
                      , 'flags': FCGI.constants.keepalive.OFF
                      })
    socket.write(writer.tobuffer())

    // Parameters
    writer.writeHeader({ 'version' : FCGI.constants.version
                       , 'type'    : FCGI.constants.record.FCGI_PARAMS
                       , 'recordId': request_id
                       , 'contentLength': FCGI.getParamLength(params)
                       , 'paddingLength': 0
                       })
    writer.writeParams(params)
    socket.write(writer.tobuffer())

    // End parameters
    writer.writeHeader({ 'version' : FCGI.constants.version
                       , 'type'    : FCGI.constants.record.FCGI_PARAMS
                       , 'recordId': request_id
                       , 'contentLength': 0
                       , 'paddingLength': 0
                       })
    socket.write(writer.tobuffer())

    // STDIN
    writer.writeHeader({ 'version' : FCGI.constants.version
                       , 'type'    : FCGI.constants.record.FCGI_STDIN
                       , 'recordId': request_id
                       , 'contentLength': 0
                       , 'paddingLength': 0
                       })
    socket.write(writer.tobuffer())
  }

  function prep_socket() {
    socket.on('data', on_data)
    socket.on('end', on_end)
  }

  function on_end() {
    connect_fcgi(socket_path, 0, function(er, new_socket) {
      if(er)
        throw er // TODO

      LOG.info('Reconnected: %s', socket_path)
      socket = new_socket
      prep_socket()
    })
  }

  function on_data(data) {
    var parser = new FCGI.parser
    parser.encoding = 'utf8'
    parser.onRecord = on_record
    parser.onError  = on_error
    parser.execute(data)
  }

  function on_error(er) {
    LOG.error('Error from FastCGI parser: %s', er.message || er)
    throw er // TODO
  }

  function on_record(record) {
    var req_id = record.header.recordId
    if(req_id == 0)
      return LOG.info('Ignoring management record: %j', record)

    var request = requests[req_id]
    if(!request)
      throw new Error('Record for unknown request: ' + req_id) // TODO

    if(typeof record.body == 'object' && JSON.stringify(record.body) == '{}')
      record.body = ''

    if(record.header.type == FCGI.constants.record.FCGI_STDERR)
      return LOG.error('Error: %s', record.body.trim())

    else if(record.header.type == FCGI.constants.record.FCGI_STDOUT) {
      return send(request, record.body)
    }

    else if(record.header.type == FCGI.constants.record.FCGI_END)
      return request.res.end()

    else {
      LOG.info('Record: %j', record)
      Object.keys(FCGI.constants.record).forEach(function(type) {
        if(record.header.type == FCGI.constants.record[type])
          LOG.info('Unknown record type: %s', type)
      })
    }
  }

  function send(request, data) {
    request.stdout.push(data)
    data = request.stdout.join('')
    request.stdout = []

    if(!request.status) {
      // Still looking for the headers and status.
      var parts = data.split(/\r?\n\r?\n/)
      if(parts.length < 2)
        return // Still waiting for all headers to arrive.

      // Headers (and perhaps some body) have arrived.
      var lines = parts[0].split(/\r?\n/)
        , headers = {}

      lines.forEach(function(line) {
        var match = line.match(/^(.*?):\s(.*)$/)
          , key = match && match[1].toLowerCase()

        if(key == 'status')
          request.status = parseInt(match[2]) || 200
        else
          headers[key] = match[2]
      })

      console.log('%d %j', request.status, headers)
      request.res.writeHead(request.status, headers)
      data = parts.slice(1).join('\n')
    }

    if(data.length) {
      console.log('Write data: %j', data)
      request.res.write(data)
    }
  }
}

function connect_fcgi(socket, attempts, callback) {
  if(attempts > 5)
    return callback(new Error('Failed to connect to back-end socket'))

  // Try to connect to the back-end socket.
  var fcgid = net.connect({'path':socket})

  fcgid.on('error', on_error)
  fcgid.on('connect', on_connect)

  function on_connect() {
    LOG.info('Connected to FastCGI daemon: %s', socket)
    fcgid.removeListener('error', on_error)
    return callback(null, fcgid)
  }

  function on_error(er) {
    if(er.code == 'ECONNREFUSED') {
      var delay = 100 * Math.pow(2, attempts)
      LOG.info('Waiting %d ms to connect', delay)
      return setTimeout(function() { connect_fcgi(socket, attempts+1, callback) }, delay)
    }

    else if(er.code == 'ENOENT') {
      LOG.error('Error: No such socket: %s', socket)
      return callback(er)
    }

    else {
      LOG.error('Unknown error on FastCGI connection: %s', er.message)
      return callback(er)
    }
  }
}

}) // defaultable
<commit_msg>Do not decode the record body because it might be binary (e.g. gzipped)<commit_after>// FastCGI
//
// Copyright 2011 Iris Couch
//
//    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.

// Specification: http://www.fastcgi.com/drupal/node/22

require('defaultable').def(module,
  { 'log': console
  }, function(module, exports, DEFS) {

var net = require('net')
var URL = require('url')
var util = require('util')
var http = require('http')
var FCGI = require('fastcgi-parser')

var LOG = DEFS.log

module.exports = { 'httpd': httpd
                 }

// Connect to a FastCGI service and run an HTTP front-end sending all requests to it.
function httpd(port, host, socket_path, callback) {
  connect_fcgi(socket_path, 0, function(er, socket) {
    if(er)
      return callback(er)

    fcgi_get_values(socket, function(er, values) {
      if(er)
        return callback(er)

      LOG.info('FCGI values: %j', values)
      var server = http.createServer(fcgi_handler(port, host, socket, socket_path))
      server.listen(port, host)
      return callback(null)
    })
  })
}

function fcgi_get_values(socket, callback) {
  LOG.info('Get FastCGI values')
  socket.on('data', on_data)

  var values = [ ['FCGI_MAX_CONNS' , '']
               , ['FCGI_MAX_REQS'  , '']
               , ['FCGI_MPXS_CONNS', '']
               ]

  var writer = new FCGI.writer
  writer.encoding = 'binary'

  writer.writeHeader({ 'version' : FCGI.constants.version
                     , 'type'    : FCGI.constants.record.FCGI_GET_VALUES
                     , 'recordId': 0
                     , 'contentLength': FCGI.getParamLength(values)
                     , 'paddingLength': 0
                     })
  writer.writeParams(values)
  socket.write(writer.tobuffer())

  writer.writeHeader({ 'version' : FCGI.constants.version
                     , 'type'    : FCGI.constants.record.FCGI_GET_VALUES
                     , 'recordId': 0
                     , 'contentLength': 0
                     , 'paddingLength': 0
                     })
  socket.write(writer.tobuffer())

  LOG.info('Listening for FastCGI values')
  var fcgi_values = {}
  var timeout = setTimeout(got_all_values, 100)

  function on_data(data) {
    var parser = new FCGI.parser
    parser.encoding = 'utf8'
    parser.onRecord = on_record
    parser.onError  = on_error
    parser.execute(data)
  }

  function on_error(er) {
    LOG.error('Error getting FastCGI values: %s', er.message || er)
    parser.onRecord = parser.onError = function() {}
    callback(er)
  }

  function on_record(record) {
    var params = record.body.params || {}
      , keys = Object.keys(params)

    keys.forEach(function(key) {
      var num_value = +params[key]
      fcgi_values[key] = isNaN(num_value) ? params[key] : num_value
    })

    if(keys.length == 0)
      got_all_values()
  }

  function got_all_values() {
    clearTimeout(timeout)
    socket.removeListener('data', on_data)
    callback(null, fcgi_values)
  }
}

function fcgi_handler(port, server_addr, socket, socket_path) {
  var requests = {}
    , request_id = 0

  prep_socket()
  return on_request

  function on_request(req, res) {
    LOG.info('Request: %j %j', req.url, req.headers)

    request_id += 1
    requests[request_id] = { 'req': req
                           , 'res':res
                           , 'stdout': []
                           , 'stderr': []
                           }

    req.url = URL.parse(req.url)
    var cgi = { 'PATH_INFO': req.url.pathname
              , 'SERVER_NAME': server_addr || 'unknown'
              , 'SERVER_PORT': port
              , 'SERVER_PROTOCOL': 'HTTP/1.1'
              , 'SERVER_SOFTWARE': 'Node/' + process.version
              }

    Object.keys(req.headers).forEach(function(header) {
      var key = 'HTTP_' + header.toUpperCase().replace(/-/g, '_')
      cgi[key] = req.headers[header]
    })

    cgi.REQUEST_METHOD = req.method
    cgi.QUERY_STRING = req.url.query || ''
    if('content-length' in req.headers)
      cgi.CONTENT_LENGTH = req.headers['content-length']
    if('content-type' in req.headers)
      cgi.CONTENT_LENGTH = req.headers['content-type']
    if('authorization' in req.headers)
      cgi.AUTH_TYPE = req.headers.authorization.split(/ /)[0]

    //LOG.info('CGI: %j', cgi)

    var params = []
    Object.keys(cgi).forEach(function(key) {
      params.push([key, cgi[key]])
    })

    // Write the request to FastCGI.
    var writer = new FCGI.writer
    writer.encoding = 'binary'

    // Begin
    writer.writeHeader({ 'version' : FCGI.constants.version
                       , 'type'    : FCGI.constants.record.FCGI_BEGIN
                       , 'recordId': request_id
                       , 'contentLength': 8
                       , 'paddingLength': 0
                       })
    writer.writeBegin({ 'role': FCGI.constants.role.FCGI_RESPONDER
                      , 'flags': FCGI.constants.keepalive.OFF
                      })
    socket.write(writer.tobuffer())

    // Parameters
    writer.writeHeader({ 'version' : FCGI.constants.version
                       , 'type'    : FCGI.constants.record.FCGI_PARAMS
                       , 'recordId': request_id
                       , 'contentLength': FCGI.getParamLength(params)
                       , 'paddingLength': 0
                       })
    writer.writeParams(params)
    socket.write(writer.tobuffer())

    // End parameters
    writer.writeHeader({ 'version' : FCGI.constants.version
                       , 'type'    : FCGI.constants.record.FCGI_PARAMS
                       , 'recordId': request_id
                       , 'contentLength': 0
                       , 'paddingLength': 0
                       })
    socket.write(writer.tobuffer())

    // STDIN
    writer.writeHeader({ 'version' : FCGI.constants.version
                       , 'type'    : FCGI.constants.record.FCGI_STDIN
                       , 'recordId': request_id
                       , 'contentLength': 0
                       , 'paddingLength': 0
                       })
    socket.write(writer.tobuffer())
  }

  function prep_socket() {
    socket.on('data', on_data)
    socket.on('end', on_end)
  }

  function on_end() {
    connect_fcgi(socket_path, 0, function(er, new_socket) {
      if(er)
        throw er // TODO

      LOG.info('Reconnected: %s', socket_path)
      socket = new_socket
      prep_socket()
    })
  }

  function on_data(data) {
    var parser = new FCGI.parser
    parser.bodies = null
    parser.encoding = 'binary'
    parser.onBody   = on_body
    parser.onRecord = on_record
    parser.onError  = on_error
    parser.execute(data)
  }

  function on_error(er) {
    LOG.error('Error from FastCGI parser: %s', er.message || er)
    throw er // TODO
  }

  function on_body(data, start, end) {
    data = data.slice(start, end)
    this.bodies = this.bodies || []
    this.bodies.push(data)
  }

  function on_record(record) {
    var parser = this

    record.bodies = parser.bodies
    parser.bodies = null
    record.body_utf8 = function() {
      return this.bodies
                 .map(function(data) { return data.toString() })
                 .join('')
    }

    var req_id = record.header.recordId
    if(req_id == 0)
      return LOG.info('Ignoring management record: %j', record)

    var request = requests[req_id]
    if(!request)
      throw new Error('Record for unknown request: ' + req_id) // TODO

    if(record.header.type == FCGI.constants.record.FCGI_STDERR)
      return LOG.error('Error: %s', record.body_utf8().trim())

    else if(record.header.type == FCGI.constants.record.FCGI_STDOUT) {
      if(!record.bodies)
        return

      request.stdout = request.stdout.concat(record.bodies)
      return send_stdout(request)
    }

    else if(record.header.type == FCGI.constants.record.FCGI_END) {
      return request.res.end()
    }

    else {
      LOG.info('Unknown record: %j', record)
      Object.keys(FCGI.constants.record).forEach(function(type) {
        if(record.header.type == FCGI.constants.record[type])
          LOG.info('Unknown record type: %s', type)
      })
    }
  }

  function send_stdout(request) {
    if(!request.status) {
      // Still looking for the headers and status.
      // TODO: I believe there is a bug here, if the headers aren't completely defined in the first data chunk.
      // The problem is, I don't want to get a chunk that has utf8 headers, then two newlines, then binary body.
      var parts = request.stdout[0].toString().split(/\r?\n\r?\n/)
      if(parts.length < 2)
        return LOG.warn('No complete headers in first stdout chunk') // Still waiting for all headers to arrive.

      // Headers have arrived. Convert the first stdout chunk into a .writeHead() and do not explicitly .write() it.
      request.stdout = request.stdout.slice(1)
      var headers_section = parts[0]
      var lines = headers_section.split(/\r?\n/)
        , headers = {}

      lines.forEach(function(line) {
        var match = line.match(/^(.*?):\s(.*)$/)
          , key = match && match[1].toLowerCase()

        if(key == 'status')
          request.status = parseInt(match[2]) || 200
        else
          headers[key] = match[2]
      })

      request.res.writeHead(request.status, headers)
    }

    while(request.stdout.length > 0) {
      var data = request.stdout.shift()
      request.res.write(data)
    }
  }
}

function connect_fcgi(socket, attempts, callback) {
  if(attempts > 5)
    return callback(new Error('Failed to connect to back-end socket'))

  // Try to connect to the back-end socket.
  var fcgid = net.connect({'path':socket})

  fcgid.on('error', on_error)
  fcgid.on('connect', on_connect)

  function on_connect() {
    LOG.info('Connected to FastCGI daemon: %s', socket)
    fcgid.removeListener('error', on_error)
    return callback(null, fcgid)
  }

  function on_error(er) {
    if(er.code == 'ECONNREFUSED') {
      var delay = 100 * Math.pow(2, attempts)
      LOG.info('Waiting %d ms to connect', delay)
      return setTimeout(function() { connect_fcgi(socket, attempts+1, callback) }, delay)
    }

    else if(er.code == 'ENOENT') {
      LOG.error('Error: No such socket: %s', socket)
      return callback(er)
    }

    else {
      LOG.error('Unknown error on FastCGI connection: %s', er.message)
      return callback(er)
    }
  }
}

}) // defaultable
<|endoftext|>


<commit_before>'use strict'

const wayfarer = require('wayfarer')
const stripUrl = require('pathname-match')
const fastJsonStringify = require('fast-json-stringify')
const fastSafeStringify = require('fast-safe-stringify')
const Ajv = require('ajv')
const jsonParser = require('body/json')

const supportedMethods = ['GET', 'POST', 'PUT']
const payloadSchema = Symbol('payloadSchema')
const outputSchema = Symbol('outputSchema')
const ajv = new Ajv({ coerceTypes: true })

const schemas = require('./lib/schemas.json')
const inputSchemaError = fastJsonStringify(schemas.inputSchemaError)

function build () {
  const router = wayfarer('/404')
  const map = new Map()
  router.on('/404', defaultRoute)

  // shorthand methods
  fastify.get = get
  fastify.post = post
  fastify.put = put
  // extended route
  fastify.route = route

  return fastify

  function fastify (req, res) {
    router(stripUrl(req.url), req, res)
  }

  function get (url, schema, handler) {
    return _route('GET', url, schema, handler)
  }

  function post (url, schema, handler) {
    return _route('POST', url, schema, handler)
  }

  function put (url, schema, handler) {
    return _route('PUT', url, schema, handler)
  }

  function _route (method, url, schema, handler) {
    if (!handler && typeof schema === 'function') {
      handler = schema
      schema = {}
    }
    return route({ method, url, schema, handler })
  }

  function route (opts) {
    if (supportedMethods.indexOf(opts.method) === -1) {
      throw new Error(`${opts.method} method is not supported!`)
    }

    if (!opts.handler) {
      throw new Error(`Missing handler function for ${opts.method}:${opts.url} route.`)
    }

    if (opts.schema && opts.schema.out) {
      opts[outputSchema] = fastJsonStringify(opts.schema.out)
    } else {
      opts[outputSchema] = fastSafeStringify
    }

    if (opts.schema && opts.schema.in) {
      opts[payloadSchema] = ajv.compile(opts.schema.in)
    }

    if (map.has(opts.url)) {
      if (map.get(opts.url)[opts.method]) {
        throw new Error(`${opts.method} already set for ${opts.url}`)
      }

      map.get(opts.url)[opts.method] = opts
    } else {
      const node = createNode(opts.url)
      node[opts.method] = opts
      map.set(opts.url, node)
    }
    return fastify
  }

  function bodyParsed (handle, params, req, res) {
    function parsed (err, body) {
      if (err) {
        res.statusCode = 422
        res.end()
        return
      }
      handleNode(handle, params, req, res, body)
    }
    return parsed
  }

  function createNode (url) {
    const node = {}

    router.on(url, function handle (params, req, res) {
      const handle = node[req.method]

      if (handle && req.method === 'GET') {
        handleNode(handle, params, req, res, null)
      } else if (handle && (req.method === 'POST' || req.method === 'PUT')) {
        if (req.headers['content-type'] === 'application/json') {
          jsonParser(req, bodyParsed(handle, params, req, res))
        } else {
          res.statusCode = 415
          res.end()
        }
      } else {
        res.statusCode = 404
        res.end()
      }
    })

    return node
  }

  function handleNode (handle, params, req, res, body) {
    if (!handle) {
      res.statusCode = 404
      res.end()
    }

    if (handle[payloadSchema] && !handle[payloadSchema](body)) {
      res.statusCode = 400
      res.end(inputSchemaError(handle[payloadSchema].errors))
      return
    }

    const request = new Request(params, req, body)
    handle.handler(request, reply)

    function reply (err, statusCode, data) {
      if (err) {
        res.statusCode = 500
        res.end()
      }

      if (!data) {
        data = statusCode
        statusCode = 200
      }

      res.statusCode = statusCode
      res.end(handle[outputSchema](data))
    }
  }

  function defaultRoute (params, req, res) {
    res.statusCode = 404
    res.end()
  }

  function Request (params, req, body) {
    this.params = params
    this.req = req
    this.body = body
  }
}

module.exports = build
<commit_msg>Added querystring validation<commit_after>'use strict'

const wayfarer = require('wayfarer')
const urlUtil = require('url')
const stripUrl = require('pathname-match')
const fastJsonStringify = require('fast-json-stringify')
const fastSafeStringify = require('fast-safe-stringify')
const Ajv = require('ajv')
const jsonParser = require('body/json')

const supportedMethods = ['GET', 'POST', 'PUT']
const ajv = new Ajv({ coerceTypes: true })

const payloadSchema = Symbol('payload-schema')
const querystringSchema = Symbol('querystring-schema')
const outputSchema = Symbol('output-schema')

const schemas = require('./lib/schemas.json')
const inputSchemaError = fastJsonStringify(schemas.inputSchemaError)

function build () {
  const router = wayfarer('/404')
  const map = new Map()
  router.on('/404', defaultRoute)

  // shorthand methods
  fastify.get = get
  fastify.post = post
  fastify.put = put
  // extended route
  fastify.route = route

  return fastify

  function fastify (req, res) {
    router(stripUrl(req.url), req, res)
  }

  function get (url, schema, handler) {
    return _route('GET', url, schema, handler)
  }

  function post (url, schema, handler) {
    return _route('POST', url, schema, handler)
  }

  function put (url, schema, handler) {
    return _route('PUT', url, schema, handler)
  }

  function _route (method, url, schema, handler) {
    if (!handler && typeof schema === 'function') {
      handler = schema
      schema = {}
    }
    return route({ method, url, schema, handler })
  }

  function route (opts) {
    if (supportedMethods.indexOf(opts.method) === -1) {
      throw new Error(`${opts.method} method is not supported!`)
    }

    if (!opts.handler) {
      throw new Error(`Missing handler function for ${opts.method}:${opts.url} route.`)
    }

    if (opts.schema && opts.schema.out) {
      opts[outputSchema] = fastJsonStringify(opts.schema.out)
    } else {
      opts[outputSchema] = fastSafeStringify
    }

    if (opts.schema && opts.schema.payload) {
      opts[payloadSchema] = ajv.compile(opts.schema.payload)
    }

    if (opts.schema && opts.schema.querystring) {
      opts[querystringSchema] = ajv.compile(opts.schema.querystring)
    }

    if (map.has(opts.url)) {
      if (map.get(opts.url)[opts.method]) {
        throw new Error(`${opts.method} already set for ${opts.url}`)
      }

      map.get(opts.url)[opts.method] = opts
    } else {
      const node = createNode(opts.url)
      node[opts.method] = opts
      map.set(opts.url, node)
    }
    return fastify
  }

  function bodyParsed (handle, params, req, res) {
    function parsed (err, body) {
      if (err) {
        res.statusCode = 422
        res.end()
        return
      }
      handleNode(handle, params, req, res, { body })
    }
    return parsed
  }

  function createNode (url) {
    const node = {}

    router.on(url, function handle (params, req, res) {
      const handle = node[req.method]

      if (handle && req.method === 'GET') {
        handleNode(handle, params, req, res, { query: urlUtil.parse(req.url, true).query })
      } else if (handle && (req.method === 'POST' || req.method === 'PUT')) {
        if (req.headers['content-type'] === 'application/json') {
          jsonParser(req, bodyParsed(handle, params, req, res))
        } else {
          res.statusCode = 415
          res.end()
        }
      } else {
        res.statusCode = 404
        res.end()
      }
    })

    return node
  }

  function handleNode (handle, params, req, res, data) {
    if (!handle) {
      res.statusCode = 404
      res.end()
    }

    if (handle[payloadSchema] && !handle[payloadSchema](data.body)) {
      res.statusCode = 400
      res.end(inputSchemaError(handle[payloadSchema].errors))
      return
    }

    if (handle[querystringSchema] && !handle[querystringSchema](data.query)) {
      res.statusCode = 400
      res.end(inputSchemaError(handle[querystringSchema].errors))
      return
    }

    const request = new Request(params, req, data.body, data.query)
    handle.handler(request, reply)

    function reply (err, statusCode, data) {
      if (err) {
        res.statusCode = 500
        res.end()
      }

      if (!data) {
        data = statusCode
        statusCode = 200
      }

      res.statusCode = statusCode
      res.end(handle[outputSchema](data))
    }
  }

  function defaultRoute (params, req, res) {
    res.statusCode = 404
    res.end()
  }

  function Request (params, req, body, query) {
    this.params = params
    this.req = req
    this.body = body
    this.query = query
  }
}

module.exports = build
<|endoftext|>


<commit_before>/* @flow */

import type fs from 'fs-extra'
import type { Metadata, MetadataSidesInfo } from '../../../../core/metadata'

const { maxDate } = require('../../../../core/timestamp')
const Pouch = require('../../../../core/pouch')

module.exports = class BaseMetadataBuilder {
  pouch: ?Pouch
  opts: {
    path: string,
    ino?: number,
    updated_at?: string|Date,
    trashed?: true,
    sides: MetadataSidesInfo
  }

  constructor (pouch: ?Pouch) {
    this.pouch = pouch
    this.opts = {
      path: 'foo',
      sides: {}
    }
  }

  ino (ino: number): this {
    this.opts.ino = ino
    return this
  }

  stats ({ino, mtime, ctime}: fs.Stats): this {
    return this.ino(ino).updatedAt(maxDate(mtime, ctime))
  }

  path (path: string): this {
    this.opts.path = path
    return this
  }

  trashed (): this {
    this.opts.trashed = true
    return this
  }

  updatedAt (date: Date): this {
    date = new Date(date)
    date.setMilliseconds(0)
    this.opts.updated_at = date.toISOString()
    return this
  }

  upToDate (): this {
    this.opts.sides = {local: 1, remote: 1}
    return this
  }

  notUpToDate (): this {
    this.opts.sides = {remote: 1}
    return this
  }

  build (): Metadata {
    throw new Error('BaseMetadataBuilder#build() not implemented')
  }

  async create (): Promise<Metadata> {
    if (this.pouch == null) {
      throw new Error('Cannot create dir metadata without Pouch')
    }
    const doc = this.build()
    // $FlowFixMe
    await this.pouch.put(doc)
    return doc
  }
}
<commit_msg>test/support/builders/metadata: Make sure created docs have a rev<commit_after>/* @flow */

import type fs from 'fs-extra'
import type { Metadata, MetadataSidesInfo } from '../../../../core/metadata'

const { maxDate } = require('../../../../core/timestamp')
const Pouch = require('../../../../core/pouch')

module.exports = class BaseMetadataBuilder {
  pouch: ?Pouch
  opts: {
    path: string,
    ino?: number,
    updated_at?: string|Date,
    trashed?: true,
    sides: MetadataSidesInfo
  }

  constructor (pouch: ?Pouch) {
    this.pouch = pouch
    this.opts = {
      path: 'foo',
      sides: {}
    }
  }

  ino (ino: number): this {
    this.opts.ino = ino
    return this
  }

  stats ({ino, mtime, ctime}: fs.Stats): this {
    return this.ino(ino).updatedAt(maxDate(mtime, ctime))
  }

  path (path: string): this {
    this.opts.path = path
    return this
  }

  trashed (): this {
    this.opts.trashed = true
    return this
  }

  updatedAt (date: Date): this {
    date = new Date(date)
    date.setMilliseconds(0)
    this.opts.updated_at = date.toISOString()
    return this
  }

  upToDate (): this {
    this.opts.sides = {local: 1, remote: 1}
    return this
  }

  notUpToDate (): this {
    this.opts.sides = {remote: 1}
    return this
  }

  build (): Metadata {
    throw new Error('BaseMetadataBuilder#build() not implemented')
  }

  async create (): Promise<Metadata> {
    if (this.pouch == null) {
      throw new Error('Cannot create dir metadata without Pouch')
    }
    const doc = this.build()
    // $FlowFixMe
    const {rev} = await this.pouch.put(doc)
    doc._rev = rev
    return doc
  }
}
<|endoftext|>


<commit_before>'use strict';
var util = require('util');
var yeoman = require('yeoman-generator');
var Step = require('step');
var spawn = require('child_process').spawn;
var git = require('gift');
var _ = require('underscore');
var exec = require('child_process').exec;
require('colors');

var UpdateGenerator = module.exports = function UpdateGenerator(args, options, config) {
  // By calling `NamedBase` here, we get the argument to the subgenerator call
  // as `this.name`.
  yeoman.generators.Base.apply(this, arguments);

};

util.inherits(UpdateGenerator, yeoman.generators.Base);

UpdateGenerator.prototype.gitUpdate = function gitUpdate() {
	var cb = this.async();

	var repo = git('./');

	function checkoutSeed(cb) {
		var checkedout = false;
		Step(function() {
			repo.branches(this);
		}, function(err, branches) {
			if (err) throw err;
			if (_.findWhere(branches, {name: 'seed'})) {
				repo.checkout('seed', this);
			} else {
				repo.checkout('-t origin/seed', this)
			}
		}, function(err) {
			cb(err);
		});
	}

	Step(function() {
		console.log('grabbing changes to pup...'.green)
		checkoutSeed(this);
	}, function(err) {
		if (err) throw err;
		exec('git pull https://github.com/themang/pup master', {maxBuffer: 10000 * 1024}, this);
	}, function(err) {
		if (err) throw err;
		console.log('merging changes...'.green);
		repo.checkout('master', this);
	}, function(err) {
		if (err) throw err;
		exec('git merge seed', {maxBuffer: 10000 * 1024}, this);
	}, function(err) {
		if (err) throw err;
		cb();
	});

};
<commit_msg>added stdio piping to git merge seed command<commit_after>'use strict';
var util = require('util');
var yeoman = require('yeoman-generator');
var Step = require('step');
var spawn = require('child_process').spawn;
var git = require('gift');
var _ = require('underscore');
var exec = require('child_process').exec;
require('colors');

var UpdateGenerator = module.exports = function UpdateGenerator(args, options, config) {
  // By calling `NamedBase` here, we get the argument to the subgenerator call
  // as `this.name`.
  yeoman.generators.Base.apply(this, arguments);

};

util.inherits(UpdateGenerator, yeoman.generators.Base);

UpdateGenerator.prototype.gitUpdate = function gitUpdate() {
	var cb = this.async();

	var repo = git('./');

	function checkoutSeed(cb) {
		var checkedout = false;
		Step(function() {
			repo.branches(this);
		}, function(err, branches) {
			if (err) throw err;
			if (_.findWhere(branches, {name: 'seed'})) {
				repo.checkout('seed', this);
			} else {
				repo.checkout('-t origin/seed', this)
			}
		}, function(err) {
			cb(err);
		});
	}

	Step(function() {
		console.log('grabbing changes to pup...'.green)
		checkoutSeed(this);
	}, function(err) {
		if (err) throw err;
		exec('git pull https://github.com/themang/pup master', {maxBuffer: 10000 * 1024}, this);
	}, function(err) {
		if (err) throw err;
		console.log('merging changes...'.green);
		repo.checkout('master', this);
	}, function(err) {
		if (err) throw err;
		 var cp = require("child_process").spawn('git', ['merge', 'seed'], {
			stdio: 'inherit'
		});
		cp.on('close', cb)
	});

};
<|endoftext|>


<commit_before>module.exports = serializeNode

var voidElements = /area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr/i;

function serializeNode(node) {
    switch (node.nodeType) {
        case 3:
            return escapeText(node.data)
        case 8:
            return "<!--" + node.data + "-->"
        default:
            return serializeElement(node)
    }
}

function serializeElement(elem) {
    var strings = []

    var tagname = elem.tagName

    if (elem.namespaceURI === "http://www.w3.org/1999/xhtml") {
        tagname = tagname.toLowerCase()
    }

    strings.push("<" + tagname + properties(elem) + datasetify(elem))

    if (voidElements.test(tagname)) {
        strings.push(" />")
    } else {
        strings.push(">")

        if (elem.childNodes.length) {
            strings.push.apply(strings, elem.childNodes.map(serializeNode))
        } else if (elem.textContent || elem.innerText) {
            strings.push(escapeText(elem.textContent || elem.innerText))
        } else if (elem.innerHTML) {
            strings.push(elem.innerHTML)
        }

        strings.push("</" + tagname + ">")
    }

    return strings.join("")
}

function isProperty(elem, key) {
    var type = typeof elem[key]

    if (key === "style" && Object.keys(elem.style).length > 0) {
      return true
    }

    return elem.hasOwnProperty(key) &&
        (type === "string" || type === "boolean" || type === "number") &&
        key !== "nodeName" && key !== "className" && key !== "tagName" &&
        key !== "textContent" && key !== "innerText" && key !== "namespaceURI" &&  key !== "innerHTML"
}

function stylify(styles) {
    if (typeof styles === 'string') return styles
    var attr = ""
    Object.keys(styles).forEach(function (key) {
        var value = styles[key]
        key = key.replace(/[A-Z]/g, function(c) {
            return "-" + c.toLowerCase();
        })
        attr += key + ":" + value + ";"
    })
    return attr
}

function datasetify(elem) {
    var ds = elem.dataset
    var props = []

    for (var key in ds) {
        props.push({ name: "data-" + key, value: ds[key] })
    }

    return props.length ? stringify(props) : ""
}

function stringify(list) {
    var attributes = []
    list.forEach(function (tuple) {
        var name = tuple.name
        var value = tuple.value

        if (name === "style") {
            value = stylify(value)
        }

        attributes.push(name + "=" + "\"" + escapeAttributeValue(value) + "\"")
    })

    return attributes.length ? " " + attributes.join(" ") : ""
}

function properties(elem) {
    var props = []
    for (var key in elem) {
        if (isProperty(elem, key)) {
            props.push({ name: key, value: elem[key] })
        }
    }

    for (var ns in elem._attributes) {
      for (var attribute in elem._attributes[ns]) {
        var prop = elem._attributes[ns][attribute]
        var name = (prop.prefix ? prop.prefix + ":" : "") + attribute
        props.push({ name: name, value: prop.value })
      }
    }

    if (elem.className) {
        props.push({ name: "class", value: elem.className })
    }

    return props.length ? stringify(props) : ""
}

function escapeText(s) {
    var str = '';

    if (typeof(s) === 'string') {
        str = s;
    } else if (s) {
        str = s.toString();
    }

    return str
        .replace(/&/g, "&")
        .replace(/</g, "<")
        .replace(/>/g, ">")
}

function escapeAttributeValue(str) {
    return escapeText(str).replace(/"/g, """)
}
<commit_msg>Change voidElements from RegEx to array<commit_after>module.exports = serializeNode

var voidElements = ["area","base","br","col","embed","hr","img","input","keygen","link","menuitem","meta","param","source","track","wbr"];

function serializeNode(node) {
    switch (node.nodeType) {
        case 3:
            return escapeText(node.data)
        case 8:
            return "<!--" + node.data + "-->"
        default:
            return serializeElement(node)
    }
}

function serializeElement(elem) {
    var strings = []

    var tagname = elem.tagName

    if (elem.namespaceURI === "http://www.w3.org/1999/xhtml") {
        tagname = tagname.toLowerCase()
    }

    strings.push("<" + tagname + properties(elem) + datasetify(elem))

    if (voidElements.indexOf(tagname) > -1) {
        strings.push(" />")
    } else {
        strings.push(">")

        if (elem.childNodes.length) {
            strings.push.apply(strings, elem.childNodes.map(serializeNode))
        } else if (elem.textContent || elem.innerText) {
            strings.push(escapeText(elem.textContent || elem.innerText))
        } else if (elem.innerHTML) {
            strings.push(elem.innerHTML)
        }

        strings.push("</" + tagname + ">")
    }

    return strings.join("")
}

function isProperty(elem, key) {
    var type = typeof elem[key]

    if (key === "style" && Object.keys(elem.style).length > 0) {
      return true
    }

    return elem.hasOwnProperty(key) &&
        (type === "string" || type === "boolean" || type === "number") &&
        key !== "nodeName" && key !== "className" && key !== "tagName" &&
        key !== "textContent" && key !== "innerText" && key !== "namespaceURI" &&  key !== "innerHTML"
}

function stylify(styles) {
    if (typeof styles === 'string') return styles
    var attr = ""
    Object.keys(styles).forEach(function (key) {
        var value = styles[key]
        key = key.replace(/[A-Z]/g, function(c) {
            return "-" + c.toLowerCase();
        })
        attr += key + ":" + value + ";"
    })
    return attr
}

function datasetify(elem) {
    var ds = elem.dataset
    var props = []

    for (var key in ds) {
        props.push({ name: "data-" + key, value: ds[key] })
    }

    return props.length ? stringify(props) : ""
}

function stringify(list) {
    var attributes = []
    list.forEach(function (tuple) {
        var name = tuple.name
        var value = tuple.value

        if (name === "style") {
            value = stylify(value)
        }

        attributes.push(name + "=" + "\"" + escapeAttributeValue(value) + "\"")
    })

    return attributes.length ? " " + attributes.join(" ") : ""
}

function properties(elem) {
    var props = []
    for (var key in elem) {
        if (isProperty(elem, key)) {
            props.push({ name: key, value: elem[key] })
        }
    }

    for (var ns in elem._attributes) {
      for (var attribute in elem._attributes[ns]) {
        var prop = elem._attributes[ns][attribute]
        var name = (prop.prefix ? prop.prefix + ":" : "") + attribute
        props.push({ name: name, value: prop.value })
      }
    }

    if (elem.className) {
        props.push({ name: "class", value: elem.className })
    }

    return props.length ? stringify(props) : ""
}

function escapeText(s) {
    var str = '';

    if (typeof(s) === 'string') {
        str = s;
    } else if (s) {
        str = s.toString();
    }

    return str
        .replace(/&/g, "&")
        .replace(/</g, "<")
        .replace(/>/g, ">")
}

function escapeAttributeValue(str) {
    return escapeText(str).replace(/"/g, """)
}
<|endoftext|>


<commit_before>enyo.kind({
	name: "enyo.AdaptiveImage",
	kind: "enyo.Image",
	published: {
		srcset: undefined, // json with property names as the ratio number and the values as the src urls
	},
	autoSize: true, // will automatically apply width/height; if false, you'll need to set your own width/height
	upperSrc: false, // If the default proposed src should be the closest match, >= than the device's
			 // detected pixel ratio, rather than the closest match <= the detected pixel ratio
			 // For example, if only a 1.0 and 2.0 image srcset was provided, with upperSrc as true
			 // and on a 1.5 ratio device, then the 2.0 image src would be used
	autoDecideSrc: false, // if true, the "onSrcProposed" event will not occur
	events: {
		onSrcProposed:"" // provides image src options; can override the default proposed src by changing
				 // the "proposedSrc" property during this event
	},
	// @public
	create: function() {
		if(this.autoDecideSrc) {
			delete this.events.onSrcProposed;
		}
		this.inherited(arguments);
		this.ratios = [];
		this.srcsetChanged();
	},
	srcsetChanged: function() {
		if(!this.srcset) {
			this.srcset = {};
		}
		for(var ratio in this.srcset) {
			var normalizedRatio = parseFloat(ratio).toString();
			if(normalizedRatio != ratio) {
				this.srcset[normalizedRatio] = this.srcset[ratio].toString();
				delete this.srcset[ratio];
				ratio = normalizedRatio;
			}
			this.ratios.push(parseFloat(ratio));
		}
		this.ratios.sort(function(a,b) { return b - a; });
		this.searchForMaxDetectedRatio();
		this.determineSrc();
	},
	searchForMaxDetectedRatio: function() {
		if(window.devicePixelRatio) {
			enyo.log("1");
			//works for most devices
			enyo.AdaptiveImage.maxDetectedRatio = parseFloat(window.devicePixelRatio);
		} else {
			//Fallback for devices that support the media queries but not window.devicePixelRatio
			//mainly this applies to Firefox v16 and earlier, as well as IE7-10
			if(window.matchMedia) {
				var matched = false;
				//go through the ratios of the specified image sources to find the largest
				//supported ratio we can provide a source for
				for(var i=0; i<this.ratios.length; i++) {
					if(enyo.AdaptiveImage.checkedRatios.indexOf(this.ratios[i])<0) {
						if(!matched && this.ratios[i]>enyo.AdaptiveImage.maxDetectedRatio) {
							//not cached, so check as it may increase precision
							if(this.mediaQuery(this.ratios[i])) {
								enyo.AdaptiveImage.maxDetectedRatio = this.ratios[i];
								matched = true;
							}
						}
						enyo.AdaptiveImage.checkedRatios.push(this.ratios[i]);
					}
				}
				if(!matched) {
					enyo.AdaptiveImage.maxDetectedRatio = 1;
				}
			} else {
				//browsers that don't support window.devicePixelRatio nor window.matchMedia are
				//outdated enough that we can assume a devicePixelRatio of 1
				enyo.AdaptiveImage.maxDetectedRatio = 1;
			}
		}
	},
	mediaQuery: function(ratio) {
		var queries = [
			"(min-resolution: " + ratio + "dppx)",
			"(min-device-pixel-ratio: " + ratio + ")",
			"(-webkit-min-device-pixel-ratio: " + ratio + ")",
			"(min--moz-device-pixel-ratio: " + ratio + ")",
			"(-ms-min-device-pixel-ratio: " + ratio + ")",
			//Opera requires fraction form
			"(-o-min-device-pixel-ratio: " + Math.toFraction(ratio) + ")"
		];
		var match = false;
		for(var q=0; q<queries.length; q++) {
			if(window.matchMedia(queries[q]).matches) {
				match = true;
				break;
			}
		}
		return match;
	},
	determineSrc: function() {
		if(this.ratios.length>0) {
			var nearest = [];
			var index = -1;
			var max = enyo.AdaptiveImage.maxDetectedRatio.toString();
			if(this.srcset[max]) {
				nearest.push({ratio:enyo.AdaptiveImage.maxDetectedRatio, src:this.srcset[max]});
				index = this.ratios.indexOf(enyo.AdaptiveImage.maxDetectedRatio);
			} else {
				for(var i=0; i<this.ratios.length; i++) {
					if(this.ratios[i]<=enyo.AdaptiveImage.maxDetectedRatio) {
						nearest.push({ratio:this.ratios[i], src:this.srcset[this.ratios[i].toString()]});
						index = i;
						break;
					}
				}
				if(firstUnder==-1) {
					this.setSrc(undefined);
					return;
				}
			}
			if(this.ratios.length>index+1) {
				nearest.push({ratio:this.ratios[index+1], src:this.srcset[this.ratios[index+1].toString()]});
			}
			//property overridable in the onSrcProposed event phase
			this.proposedSrc = (nearest.length>1 && this.upperSrc) ? nearest[1].src : nearest[0].src;
			if(!this.autoDecideSrc) {
				//give the developer an option to specify a different src from the srcset, and offer recommendations
				this.doSrcProposed({closestOptions:nearest, srcset:this.srcset});
			}
			if(this.autoSize) {
				this.determineImageSize(this.proposedSrc, enyo.bind(this, function(response) {
					var currRatio = this.getRatioBySrc(this.proposedSrc);
					this.applyStyle("width", (response.width/currRatio) + "px");
					this.applyStyle("height", (response.height/currRatio) + "px");
					this.setSrc(this.proposedSrc);
				}));
			} else {
				this.setSrc(this.proposedSrc);
			}
		} else {
			this.setSrc(undefined);
		}
	},
	determineImageSize: function(src, callback) {
		var img = new Image();
		img.onload = function(inEvent) {
			callback({width:img.width, height:img.height});
		};
		img.onerror = enyo.bind(this, function(inError) {
			this.bubble("onerror", inError);
		});
		img.src = src;
	},
	// @public
	setSrcByRatio: function(src, ratio) {
		ratio = parseFloat(ratio);
		this.srcset[ratio.toString()] = src;
		this.ratios.push(ratio);
		this.ratios.sort(function(a,b){return b-a});
		if(ratio == enyo.AdaptiveImage.maxDetectedRatio) {
			this.determineSrc();
		} else if(!window.devicePixelRatio && enyo.AdaptiveImage.checkedRatios.indexOf(ratio)<0) {
			if(window.matchMedia && this.mediaQuery(ratio) && enyo.AdaptiveImage.maxDetectedRatio<ratio) {
				enyo.AdaptiveImage.maxDetectedRatio = ratio;
				this.determineSrc();
			}
			enyo.AdaptiveImage.checkedRatios.push(ratio);
		}
	},
	removeSrcByRatio: function(src, ratio) {
		ratio = parseFloat(ratio);
		this.srcset[ratio.toString()] = undefined;
		this.ratios.splice(this.ratios.indexOf(ratio), 1);
		if(this.src == src) {
			this.determineSrc();
		}
	},
	getSrcByRatio: function(ratio) {
		ratio = parseFloat(ratio);
		return this.srcset[ratio.toString()];
	},
	getCurrentRatio: function() {
		return this.getRatioBySrc(this.src);
	},
	getRatioBySrc: function(src) {
		var result = undefined;
		for(var ratio in this.srcset) {
			if(this.srcset[ratio] == src) {
				result = parseFloat(ratio);
				break;
			}
		}
		return result;
	},
	statics: {
		//will be determined at runtime
		maxDetectedRatio: 0,
		//cache of ratios compared against; static for effiency in multiple AdaptiveImage usage
		checkedRatios: []
	}
});

Math.toFraction = function(d) {
    var top = d.toString();
    top = (top.indexOf("\.")>-1) ? top.replace(/\d+[.]/, '') : "0";
    var bot = Math.pow(10, top.length);
    if (d > 1) {
	top = +top + Math.floor(d) * bot;
    }
    var x = function gcd(a, b) {
	return (b) ? gcd(b, a % b) : a;
    }(top, bot);
    return (top / x) + "/" + (bot / x);
};

<commit_msg>Cleanup<commit_after>enyo.kind({
	name: "enyo.AdaptiveImage",
	kind: "enyo.Image",
	published: {
		srcset: undefined, // json with property names as the ratio number and the values as the src urls
	},
	autoSize: true, // will automatically apply width/height; if false, you'll need to set your own width/height
	upperSrc: false, // If the default proposed src should be the closest match, >= than the device's
			 // detected pixel ratio, rather than the closest match <= the detected pixel ratio
			 // For example, if only a 1.0 and 2.0 image srcset was provided, with upperSrc as true
			 // and on a 1.5 ratio device, then the 2.0 image src would be used
	autoDecideSrc: false, // if true, the "onSrcProposed" event will not occur
	events: {
		onSrcProposed:"" // provides image src options; can override the default proposed src by changing
				 // the "proposedSrc" property during this event
	},
	// @public
	create: function() {
		if(this.autoDecideSrc) {
			delete this.events.onSrcProposed;
		}
		this.inherited(arguments);
		this.ratios = [];
		this.srcsetChanged();
	},
	srcsetChanged: function() {
		if(!this.srcset) {
			this.srcset = {};
		}
		for(var ratio in this.srcset) {
			var normalizedRatio = parseFloat(ratio).toString();
			if(normalizedRatio != ratio) {
				this.srcset[normalizedRatio] = this.srcset[ratio].toString();
				delete this.srcset[ratio];
				ratio = normalizedRatio;
			}
			this.ratios.push(parseFloat(ratio));
		}
		this.ratios.sort(function(a,b) { return b - a; });
		this.searchForMaxDetectedRatio();
		this.determineSrc();
	},
	searchForMaxDetectedRatio: function() {
		if(window.devicePixelRatio) {
			//works for most devices
			enyo.AdaptiveImage.maxDetectedRatio = parseFloat(window.devicePixelRatio);
		} else {
			//Fallback for devices that support the media queries but not window.devicePixelRatio
			//mainly this applies to Firefox v16 and earlier, as well as IE7-10
			if(window.matchMedia) {
				var matched = false;
				//go through the ratios of the specified image sources to find the largest
				//supported ratio we can provide a source for
				for(var i=0; i<this.ratios.length; i++) {
					if(enyo.AdaptiveImage.checkedRatios.indexOf(this.ratios[i])<0) {
						if(!matched && this.ratios[i]>enyo.AdaptiveImage.maxDetectedRatio) {
							//not cached, so check as it may increase precision
							if(this.mediaQuery(this.ratios[i])) {
								enyo.AdaptiveImage.maxDetectedRatio = this.ratios[i];
								matched = true;
							}
						}
						enyo.AdaptiveImage.checkedRatios.push(this.ratios[i]);
					}
				}
				if(!matched) {
					enyo.AdaptiveImage.maxDetectedRatio = 1;
				}
			} else {
				//browsers that don't support window.devicePixelRatio nor window.matchMedia are
				//outdated enough that we can assume a devicePixelRatio of 1
				enyo.AdaptiveImage.maxDetectedRatio = 1;
			}
		}
	},
	mediaQuery: function(ratio) {
		var queries = [
			"(min-resolution: " + ratio + "dppx)",
			"(min-device-pixel-ratio: " + ratio + ")",
			"(-webkit-min-device-pixel-ratio: " + ratio + ")",
			"(min--moz-device-pixel-ratio: " + ratio + ")",
			"(-ms-min-device-pixel-ratio: " + ratio + ")",
			//Opera requires fraction form
			"(-o-min-device-pixel-ratio: " + Math.toFraction(ratio) + ")"
		];
		var match = false;
		for(var q=0; q<queries.length; q++) {
			if(window.matchMedia(queries[q]).matches) {
				match = true;
				break;
			}
		}
		return match;
	},
	determineSrc: function() {
		if(this.ratios.length>0) {
			var nearest = [];
			var index = -1;
			var max = enyo.AdaptiveImage.maxDetectedRatio.toString();
			if(this.srcset[max]) {
				nearest.push({ratio:enyo.AdaptiveImage.maxDetectedRatio, src:this.srcset[max]});
				index = this.ratios.indexOf(enyo.AdaptiveImage.maxDetectedRatio);
			} else {
				for(var i=0; i<this.ratios.length; i++) {
					if(this.ratios[i]<=enyo.AdaptiveImage.maxDetectedRatio) {
						nearest.push({ratio:this.ratios[i], src:this.srcset[this.ratios[i].toString()]});
						index = i;
						break;
					}
				}
				if(firstUnder==-1) {
					this.setSrc(undefined);
					return;
				}
			}
			if(this.ratios.length>index+1) {
				nearest.push({ratio:this.ratios[index+1], src:this.srcset[this.ratios[index+1].toString()]});
			}
			//property overridable in the onSrcProposed event phase
			this.proposedSrc = (nearest.length>1 && this.upperSrc) ? nearest[1].src : nearest[0].src;
			if(!this.autoDecideSrc) {
				//give the developer an option to specify a different src from the srcset, and offer recommendations
				this.doSrcProposed({closestOptions:nearest, srcset:this.srcset});
			}
			if(this.autoSize) {
				this.determineImageSize(this.proposedSrc, enyo.bind(this, function(response) {
					var currRatio = this.getRatioBySrc(this.proposedSrc);
					this.applyStyle("width", (response.width/currRatio) + "px");
					this.applyStyle("height", (response.height/currRatio) + "px");
					this.setSrc(this.proposedSrc);
				}));
			} else {
				this.setSrc(this.proposedSrc);
			}
		} else {
			this.setSrc(undefined);
		}
	},
	determineImageSize: function(src, callback) {
		var img = new Image();
		img.onload = function(inEvent) {
			callback({width:img.width, height:img.height});
		};
		img.onerror = enyo.bind(this, function(inError) {
			this.bubble("onerror", inError);
		});
		img.src = src;
	},
	// @public
	setSrcByRatio: function(src, ratio) {
		ratio = parseFloat(ratio);
		this.srcset[ratio.toString()] = src;
		this.ratios.push(ratio);
		this.ratios.sort(function(a,b){return b-a});
		if(ratio == enyo.AdaptiveImage.maxDetectedRatio) {
			this.determineSrc();
		} else if(!window.devicePixelRatio && enyo.AdaptiveImage.checkedRatios.indexOf(ratio)<0) {
			if(window.matchMedia && this.mediaQuery(ratio) && enyo.AdaptiveImage.maxDetectedRatio<ratio) {
				enyo.AdaptiveImage.maxDetectedRatio = ratio;
				this.determineSrc();
			}
			enyo.AdaptiveImage.checkedRatios.push(ratio);
		}
	},
	removeSrcByRatio: function(src, ratio) {
		ratio = parseFloat(ratio);
		this.srcset[ratio.toString()] = undefined;
		this.ratios.splice(this.ratios.indexOf(ratio), 1);
		if(this.src == src) {
			this.determineSrc();
		}
	},
	getSrcByRatio: function(ratio) {
		ratio = parseFloat(ratio);
		return this.srcset[ratio.toString()];
	},
	getCurrentRatio: function() {
		return this.getRatioBySrc(this.src);
	},
	getRatioBySrc: function(src) {
		var result = undefined;
		for(var ratio in this.srcset) {
			if(this.srcset[ratio] == src) {
				result = parseFloat(ratio);
				break;
			}
		}
		return result;
	},
	statics: {
		//will be determined at runtime
		maxDetectedRatio: 0,
		//cache of ratios compared against; static for effiency in multiple AdaptiveImage usage
		checkedRatios: []
	}
});

Math.toFraction = function(d) {
    var top = d.toString();
    top = (top.indexOf("\.")>-1) ? top.replace(/\d+[.]/, '') : "0";
    var bot = Math.pow(10, top.length);
    if (d > 1) {
	top = +top + Math.floor(d) * bot;
    }
    var x = function gcd(a, b) {
	return (b) ? gcd(b, a % b) : a;
    }(top, bot);
    return (top / x) + "/" + (bot / x);
};

<|endoftext|>


<commit_before>/*
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of iotagent-ul
 *
 * iotagent-ul is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * iotagent-ul is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY 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 iotagent-ul.
 * If not, seehttp://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::[iot_support@tid.es]
 */

'use strict';

var iotagentJson = require('../../'),
    config = require('../config-test.js'),
    nock = require('nock'),
    iotAgentLib = require('iotagent-node-lib'),
    should = require('should'),
    request = require('request'),
    utils = require('../utils'),
    mockedClientServer,
    contextBrokerMock;

describe('Data Bidirectionality: HTTP', function() {
    var notificationOptions = {
        url: 'http://localhost:' + config.iota.server.port + '/notify',
        method: 'POST',
        json: utils.readExampleFile('./test/subscriptions/bidirectionalNotification.json'),
        headers: {
            'fiware-service': 'smartGondor',
            'fiware-servicepath': '/gardens'
        }
    };

    afterEach(function(done) {
        nock.cleanAll();

        iotAgentLib.clearAll(function() {
            iotagentJson.stop(done);
        });
    });

    describe('When a bidirectional attribute is set and a new value arrives to a device without endpoint', function() {
        beforeEach(function(done) {
            var provisionOptions = {
                url: 'http://localhost:' + config.iota.server.port + '/iot/devices',
                method: 'POST',
                json: utils.readExampleFile('./test/deviceProvisioning/provisionCommandBidirectional.json'),
                headers: {
                    'fiware-service': 'smartGondor',
                    'fiware-servicepath': '/gardens'
                }
            };

            nock.cleanAll();

            contextBrokerMock = nock('http://192.168.1.1:1026')
                .matchHeader('fiware-service', 'smartGondor')
                .matchHeader('fiware-servicepath', '/gardens')
                .post('/v1/subscribeContext', utils.readExampleFile(
                    './test//subscriptions/bidirectionalSubscriptionRequest.json'))
                .reply(200, utils.readExampleFile(
                    './test/subscriptionResponses/bidirectionalSubscriptionSuccess.json'));

            contextBrokerMock
                .matchHeader('fiware-service', 'smartGondor')
                .matchHeader('fiware-servicepath', '/gardens')
                .post('/v1/updateContext', utils.readExampleFile(
                    './test/contextRequests/createBidirectionalDevice.json'))
                .reply(200, utils.readExampleFile(
                    './test/contextResponses/createBidirectionalDeviceSuccess.json'));

            contextBrokerMock = nock('http://192.168.1.1:1026')
                .matchHeader('fiware-service', 'smartGondor')
                .matchHeader('fiware-servicepath', '/gardens')
                .post('/v1/unsubscribeContext', utils.readExampleFile(
                    './test/subscriptions/simpleSubscriptionRemove.json'))
                .reply(200, utils.readExampleFile(
                    './test/subscriptionResponses/bidirectionalSubscriptionSuccess.json'));

            iotagentJson.start(config, function(error) {
                request(provisionOptions, function(error, response, body) {
                    done();
                });
            });
        });

        it('should return a 200 OK', function(done) {
            request(notificationOptions, function(error, response, body) {
                should.not.exist(error);
                response.statusCode.should.equal(200);
                done();
            });
        });

        it('should leave the data in the polling queue', function(done) {
            request(notificationOptions, function(error, response, body) {
                iotAgentLib.commandQueue('smartGondor', '/gardens', 'MQTT_2', function(error, list) {
                    should.not.exist(error);

                    list.commands.length.should.equal(3);
                    done();
                });
            });
        });

        it('should send all the data from the notification in command syntax', function(done) {
            request(notificationOptions, function(error, response, body) {
                iotAgentLib.commandQueue('smartGondor', '/gardens', 'MQTT_2', function(error, list) {
                    var latitudeFound = false,
                        longitudeFound = false;

                    for (var i = 0; i < list.commands.length; i++) {
                        if (list.commands[i].name === 'latitude' &&
                            list.commands[i].type === 'string' &&
                            list.commands[i].value === '-9.6') {
                            latitudeFound = true;
                        }

                        if (list.commands[i].name === 'longitude' &&
                            list.commands[i].type === 'string' &&
                            list.commands[i].value === '12.4') {
                            longitudeFound = true;
                        }
                    }

                    latitudeFound.should.equal(true);
                    longitudeFound.should.equal(true);

                    done();
                });
            });
        });
    });

    describe('When a bidirectional attribute is set and a new value arrives to a device with endpoint', function() {
        beforeEach(function(done) {
            var provisionOptions = {
                url: 'http://localhost:' + config.iota.server.port + '/iot/devices',
                method: 'POST',
                json: utils.readExampleFile('./test/deviceProvisioning/provisionCommandBidirectionalWithUrl.json'),
                headers: {
                    'fiware-service': 'smartGondor',
                    'fiware-servicepath': '/gardens'
                }
            };

            nock.cleanAll();

            contextBrokerMock = nock('http://192.168.1.1:1026')
                .matchHeader('fiware-service', 'smartGondor')
                .matchHeader('fiware-servicepath', '/gardens')
                .post('/v1/subscribeContext', utils.readExampleFile(
                    './test//subscriptions/bidirectionalSubscriptionRequest.json'))
                .reply(200, utils.readExampleFile(
                    './test/subscriptionResponses/bidirectionalSubscriptionSuccess.json'));

            contextBrokerMock
                .matchHeader('fiware-service', 'smartGondor')
                .matchHeader('fiware-servicepath', '/gardens')
                .post('/v1/updateContext', utils.readExampleFile(
                    './test/contextRequests/createBidirectionalDevice.json'))
                .reply(200, utils.readExampleFile(
                    './test/contextResponses/createBidirectionalDeviceSuccess.json'));

            contextBrokerMock = nock('http://192.168.1.1:1026')
                .matchHeader('fiware-service', 'smartGondor')
                .matchHeader('fiware-servicepath', '/gardens')
                .post('/v1/unsubscribeContext', utils.readExampleFile(
                    './test/subscriptions/simpleSubscriptionRemove.json'))
                .reply(200, utils.readExampleFile(
                    './test/subscriptionResponses/bidirectionalSubscriptionSuccess.json'));

            mockedClientServer = nock('http://localhost:9876')
                .post('/command', '{"location":"12.4, -9.6"}')
                .reply(200, '')
                .post('/command', '{"latitude":"-9.6"}')
                .reply(200, '')
                .post('/command', '{"longitude":"12.4"}')
                .reply(200, '');

            iotagentJson.start(config, function(error) {
                request(provisionOptions, function(error, response, body) {
                    done();
                });
            });
        });

        it('should return a 200 OK', function(done) {
            request(notificationOptions, function(error, response, body) {
                should.not.exist(error);
                response.statusCode.should.equal(200);
                done();
            });
        });

        it('should send all the data from the notification in command syntax', function(done) {
            request(notificationOptions, function(error, response, body) {
                mockedClientServer.done();
                done();
            });
        });
    });
});
<commit_msg>FIX Push commands dont return<commit_after>/*
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of iotagent-ul
 *
 * iotagent-ul is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * iotagent-ul is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY 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 iotagent-ul.
 * If not, seehttp://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::[iot_support@tid.es]
 */

'use strict';

var iotagentJson = require('../../'),
    config = require('../config-test.js'),
    nock = require('nock'),
    iotAgentLib = require('iotagent-node-lib'),
    should = require('should'),
    request = require('request'),
    utils = require('../utils'),
    mockedClientServer,
    contextBrokerMock;

describe.only('Data Bidirectionality: HTTP', function() {
    var notificationOptions = {
        url: 'http://localhost:' + config.iota.server.port + '/notify',
        method: 'POST',
        json: utils.readExampleFile('./test/subscriptions/bidirectionalNotification.json'),
        headers: {
            'fiware-service': 'smartGondor',
            'fiware-servicepath': '/gardens'
        }
    };

    afterEach(function(done) {
        nock.cleanAll();

        iotAgentLib.clearAll(function() {
            iotagentJson.stop(done);
        });
    });

    describe('When a bidirectional attribute is set and a new value arrives to a device without endpoint', function() {
        beforeEach(function(done) {
            var provisionOptions = {
                url: 'http://localhost:' + config.iota.server.port + '/iot/devices',
                method: 'POST',
                json: utils.readExampleFile('./test/deviceProvisioning/provisionCommandBidirectional.json'),
                headers: {
                    'fiware-service': 'smartGondor',
                    'fiware-servicepath': '/gardens'
                }
            };

            nock.cleanAll();

            contextBrokerMock = nock('http://192.168.1.1:1026')
                .matchHeader('fiware-service', 'smartGondor')
                .matchHeader('fiware-servicepath', '/gardens')
                .post('/v1/subscribeContext', utils.readExampleFile(
                    './test//subscriptions/bidirectionalSubscriptionRequest.json'))
                .reply(200, utils.readExampleFile(
                    './test/subscriptionResponses/bidirectionalSubscriptionSuccess.json'));

            contextBrokerMock
                .matchHeader('fiware-service', 'smartGondor')
                .matchHeader('fiware-servicepath', '/gardens')
                .post('/v1/updateContext', utils.readExampleFile(
                    './test/contextRequests/createBidirectionalDevice.json'))
                .reply(200, utils.readExampleFile(
                    './test/contextResponses/createBidirectionalDeviceSuccess.json'));

            contextBrokerMock = nock('http://192.168.1.1:1026')
                .matchHeader('fiware-service', 'smartGondor')
                .matchHeader('fiware-servicepath', '/gardens')
                .post('/v1/unsubscribeContext', utils.readExampleFile(
                    './test/subscriptions/simpleSubscriptionRemove.json'))
                .reply(200, utils.readExampleFile(
                    './test/subscriptionResponses/bidirectionalSubscriptionSuccess.json'));

            iotagentJson.start(config, function(error) {
                request(provisionOptions, function(error, response, body) {
                    done();
                });
            });
        });

        it('should return a 200 OK', function(done) {
            request(notificationOptions, function(error, response, body) {
                should.not.exist(error);
                response.statusCode.should.equal(200);
                done();
            });
        });

        it('should leave the data in the polling queue', function(done) {
            request(notificationOptions, function(error, response, body) {
                iotAgentLib.commandQueue('smartGondor', '/gardens', 'MQTT_2', function(error, list) {
                    should.not.exist(error);

                    list.commands.length.should.equal(3);
                    done();
                });
            });
        });

        it('should send all the data from the notification in command syntax', function(done) {
            request(notificationOptions, function(error, response, body) {
                iotAgentLib.commandQueue('smartGondor', '/gardens', 'MQTT_2', function(error, list) {
                    var latitudeFound = false,
                        longitudeFound = false;

                    for (var i = 0; i < list.commands.length; i++) {
                        if (list.commands[i].name === 'latitude' &&
                            list.commands[i].type === 'string' &&
                            list.commands[i].value === '-9.6') {
                            latitudeFound = true;
                        }

                        if (list.commands[i].name === 'longitude' &&
                            list.commands[i].type === 'string' &&
                            list.commands[i].value === '12.4') {
                            longitudeFound = true;
                        }
                    }

                    latitudeFound.should.equal(true);
                    longitudeFound.should.equal(true);

                    done();
                });
            });
        });
    });

    describe('When a bidirectional attribute is set and a new value arrives to a device with endpoint', function() {
        beforeEach(function(done) {
            var provisionOptions = {
                url: 'http://localhost:' + config.iota.server.port + '/iot/devices',
                method: 'POST',
                json: utils.readExampleFile('./test/deviceProvisioning/provisionCommandBidirectionalWithUrl.json'),
                headers: {
                    'fiware-service': 'smartGondor',
                    'fiware-servicepath': '/gardens'
                }
            };

            nock.cleanAll();

            contextBrokerMock = nock('http://192.168.1.1:1026')
                .matchHeader('fiware-service', 'smartGondor')
                .matchHeader('fiware-servicepath', '/gardens')
                .post('/v1/subscribeContext', utils.readExampleFile(
                    './test//subscriptions/bidirectionalSubscriptionRequest.json'))
                .reply(200, utils.readExampleFile(
                    './test/subscriptionResponses/bidirectionalSubscriptionSuccess.json'));

            contextBrokerMock
                .matchHeader('fiware-service', 'smartGondor')
                .matchHeader('fiware-servicepath', '/gardens')
                .post('/v1/updateContext', utils.readExampleFile(
                    './test/contextRequests/createBidirectionalDevice.json'))
                .reply(200, utils.readExampleFile(
                    './test/contextResponses/createBidirectionalDeviceSuccess.json'));

            contextBrokerMock = nock('http://192.168.1.1:1026')
                .matchHeader('fiware-service', 'smartGondor')
                .matchHeader('fiware-servicepath', '/gardens')
                .post('/v1/unsubscribeContext', utils.readExampleFile(
                    './test/subscriptions/simpleSubscriptionRemove.json'))
                .reply(200, utils.readExampleFile(
                    './test/subscriptionResponses/bidirectionalSubscriptionSuccess.json'));

            mockedClientServer = nock('http://localhost:9876')
                .post('/command', '{"location":"12.4, -9.6"}')
                .reply(200, '')
                .post('/command', '{"latitude":"-9.6"}')
                .reply(200, '')
                .post('/command', '{"longitude":"12.4"}')
                .reply(200, '');

            iotagentJson.start(config, function(error) {
                request(provisionOptions, function(error, response, body) {
                    done();
                });
            });
        });

        it('should return a 200 OK', function(done) {
            request(notificationOptions, function(error, response, body) {
                should.not.exist(error);
                response.statusCode.should.equal(200);
                done();
            });
        });

        it('should send all the data from the notification in command syntax', function(done) {
            request(notificationOptions, function(error, response, body) {
                mockedClientServer.done();
                done();
            });
        });
    });
});
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Component.extend(Ember.Evented, {
  tagName: 'input',
  type: 'file',
  attributeBindings: ['type', 'multiple', 'accept'],
  multiple: false,
  change (event) {
    const input = event.target;
    if (!Ember.isEmpty(input.files)) {
      this.trigger('filesDidChange', input.files);
    }
  }
});
<commit_msg>Add attributeBindings for W3 permitted attributes<commit_after>import Ember from 'ember';

export default Ember.Component.extend(Ember.Evented, {
  tagName: 'input',
  type: 'file',
  attributeBindings: [
    'name',
    'disabled',
    'form',
    'type',
    'accept',
    'autofocus',
    'required',
    'multiple'
  ],
  multiple: false,
  change (event) {
    const input = event.target;
    if (!Ember.isEmpty(input.files)) {
      this.trigger('filesDidChange', input.files);
    }
  }
});
<|endoftext|>


<commit_before>var common       = require('../../common');
var assert       = require('assert');
var test         = require('utest');
var parseNavdata = require(common.lib + '/navdata/parseNavdata');
var fs           = require('fs');
var fixture      = fs.readFileSync(common.fixtures + '/navdata.bin');

test('parseNavdata', {
  'parses main payload': function() {
    var navdata = parseNavdata(fixture);

    assert.equal(navdata.header, parseNavdata.NAVDATA_HEADER);

    var droneState = navdata.droneState;
    assert.equal(droneState.flying, 0);
    assert.equal(droneState.videoEnabled, 0);
    assert.equal(droneState.visionEnabled, 0);
    assert.equal(droneState.controlAlgorithm, 0);
    assert.equal(droneState.altitudeControlAlgorithm, 1);
    assert.equal(droneState.startButtonState, 0);
    assert.equal(droneState.controlCommandAck, 1);
    assert.equal(droneState.cameraReady, 1);
    assert.equal(droneState.travellingEnabled, 0);
    assert.equal(droneState.usbReady, 0);
    assert.equal(droneState.navdataDemo, 0);
    assert.equal(droneState.navdataBootstrap, 0);
    assert.equal(droneState.motorProblem, 0);
    assert.equal(droneState.communicationLost, 0);
    assert.equal(droneState.softwareFault, 0);
    assert.equal(droneState.lowBattery, 0);
    assert.equal(droneState.userEmergencyLanding, 0);
    assert.equal(droneState.timerElapsed, 0);
    assert.equal(droneState.MagnometerNeedsCalibration, 0);
    assert.equal(droneState.anglesOutOfRange, 0);
    assert.equal(droneState.tooMuchWind, 0);
    assert.equal(droneState.ultrasonicSensorDeaf, 0);
    assert.equal(droneState.cutoutDetected, 0);
    assert.equal(droneState.picVersionNumberOk, 1);
    assert.equal(droneState.atCodecThreadOn, 1);
    assert.equal(droneState.navdataThreadOn, 1);
    assert.equal(droneState.videoThreadOn, 1);
    assert.equal(droneState.acquisitionThreadOn, 1);
    assert.equal(droneState.controlWatchdogDelay, 0);
    assert.equal(droneState.adcWatchdogDelay, 0);
    assert.equal(droneState.comWatchdogProblem, 1);
    assert.equal(droneState.emergencyLanding, 0);

    assert.equal(navdata.sequenceNumber, 300711);
    assert.equal(navdata.visionFlag, 1);
  },

  'parses demo option': function() {
    var demo = parseNavdata(fixture).demo;
    assert.equal(demo.flyState, 'FLYING_OK');
    assert.equal(demo.controlState, 'CTRL_LANDED');
    assert.equal(demo.batteryPercentage, 50);
    assert.equal(demo.frontBackDegrees, 2.974);
    assert.equal(demo.leftRightDegrees, 0.55);
    assert.equal(demo.clockwiseDegrees, 1.933);
    assert.equal(demo.altitudeMeters, 0);
    assert.equal(demo.xVelocity, 0.0585307739675045);
    assert.equal(demo.yVelocity, -0.8817979097366333);
    assert.equal(demo.zVelocity, 0);
  },

  'parses wifi option': function() {
    var wifi = parseNavdata(fixture).wifi;
    assert.equal(wifi.linkQuality, 1);
  },

  'parses time option': function() {
    var time = parseNavdata(fixture).time;
    assert.equal(time, 362979.125);
  },

  'parses rawMeasures option': function() {
    var rawMeasures = parseNavdata(fixture).rawMeasures;

    assert.equal(rawMeasures.accelerometers.x, 2040);
    assert.equal(rawMeasures.accelerometers.y, 2036);
    assert.equal(rawMeasures.accelerometers.z, 2528);

    assert.equal(rawMeasures.gyrometers.x, -23);
    assert.equal(rawMeasures.gyrometers.y, 15);
    assert.equal(rawMeasures.gyrometers.z, 0);

    assert.equal(rawMeasures.gyrometers110[0], 0);
    assert.equal(rawMeasures.gyrometers110[1], 0);
    assert.equal(rawMeasures.gyrometers110.length, 2);

    assert.equal(rawMeasures.batteryMilliVolt, 11686);
    assert.equal(rawMeasures.usDebutEcho, 0);
    assert.equal(rawMeasures.usFinEcho, 0);
    assert.equal(rawMeasures.usAssociationEcho, 3758);
    assert.equal(rawMeasures.usDistanceEcho, 0);
    assert.equal(rawMeasures.usCourbeTemps, 21423);
    assert.equal(rawMeasures.usCourbeValeur, 0);
    assert.equal(rawMeasures.usCourbeRef, 120);
    assert.equal(rawMeasures.flagEchoIni, 1);
    assert.equal(rawMeasures.nbEcho, 1);
    assert.equal(rawMeasures.sumEcho, 3539193);
    assert.equal(rawMeasures.altTempRaw, 243);
  },

  'parses pwm option': function() {
    var pwm = parseNavdata(fixture).pwm;

    assert.deepEqual(pwm.motors, [0, 0, 0, 0]);
    assert.deepEqual(pwm.satMotors, [255, 255, 255, 255]);
    assert.equal(pwm.gazFeedForward, 0);
    assert.equal(pwm.gazAltitude, 0);
    assert.equal(pwm.altitudeIntegral, 0);
    assert.equal(pwm.vzRef, 0);
    assert.equal(pwm.uPitch, 0);
    assert.equal(pwm.uRoll, 0);
    assert.equal(pwm.uYaw, 0);
    assert.equal(pwm.yawUI, 0);
    assert.equal(pwm.uPitchPlanif, 0);
    assert.equal(pwm.uRollPlanif, 0);
    assert.equal(pwm.uYawPlanif, 0);
    assert.equal(pwm.uGazPlanif, 0);
    assert.deepEqual(pwm.motorCurrents, [0, 0, 0, 0]);
    assert.equal(pwm.altitudeProp, 0);
    assert.equal(pwm.altitudeDer, 0);
  },

  'parses watchdog option': function() {
    var watchdog = parseNavdata(fixture).watchdog;
    assert.equal(watchdog, 4822);
  },

  'throws exception on invalid header': function() {
    assert.throws(function() {
      parseNavdata(new Buffer([1, 2, 3, 4]));
    }, /header/i);
  },

  'detects bad checksum': function() {
    // hacky way to get a copy of our fixture
    var fixtureCopy = Buffer.concat([new Buffer(0), fixture]);

    // corrupt a byte inside our fixture
    fixtureCopy[23] = fixtureCopy[23] + 1;

    assert.throws(function() {
      parseNavdata(fixtureCopy)
    }, /checksum/i);
  },

  'no infinite loop when checksum is cut off': function() {
    var incompleteFixture = Buffer.concat([
      new Buffer(0),
      fixture.slice(0, fixture.length - 8) // strip checksum from end
    ]);

    assert.throws(function() {
      parseNavdata(incompleteFixture)
    }, /beyond/i);
  },
});
<commit_msg>Add tests for data structure introduced in 5319cfc.<commit_after>var common       = require('../../common');
var assert       = require('assert');
var test         = require('utest');
var parseNavdata = require(common.lib + '/navdata/parseNavdata');
var fs           = require('fs');
var fixture      = fs.readFileSync(common.fixtures + '/navdata.bin');

test('parseNavdata', {
  'parses main payload': function() {
    var navdata = parseNavdata(fixture);

    assert.equal(navdata.header, parseNavdata.NAVDATA_HEADER);

    var droneState = navdata.droneState;
    assert.equal(droneState.flying, 0);
    assert.equal(droneState.videoEnabled, 0);
    assert.equal(droneState.visionEnabled, 0);
    assert.equal(droneState.controlAlgorithm, 0);
    assert.equal(droneState.altitudeControlAlgorithm, 1);
    assert.equal(droneState.startButtonState, 0);
    assert.equal(droneState.controlCommandAck, 1);
    assert.equal(droneState.cameraReady, 1);
    assert.equal(droneState.travellingEnabled, 0);
    assert.equal(droneState.usbReady, 0);
    assert.equal(droneState.navdataDemo, 0);
    assert.equal(droneState.navdataBootstrap, 0);
    assert.equal(droneState.motorProblem, 0);
    assert.equal(droneState.communicationLost, 0);
    assert.equal(droneState.softwareFault, 0);
    assert.equal(droneState.lowBattery, 0);
    assert.equal(droneState.userEmergencyLanding, 0);
    assert.equal(droneState.timerElapsed, 0);
    assert.equal(droneState.MagnometerNeedsCalibration, 0);
    assert.equal(droneState.anglesOutOfRange, 0);
    assert.equal(droneState.tooMuchWind, 0);
    assert.equal(droneState.ultrasonicSensorDeaf, 0);
    assert.equal(droneState.cutoutDetected, 0);
    assert.equal(droneState.picVersionNumberOk, 1);
    assert.equal(droneState.atCodecThreadOn, 1);
    assert.equal(droneState.navdataThreadOn, 1);
    assert.equal(droneState.videoThreadOn, 1);
    assert.equal(droneState.acquisitionThreadOn, 1);
    assert.equal(droneState.controlWatchdogDelay, 0);
    assert.equal(droneState.adcWatchdogDelay, 0);
    assert.equal(droneState.comWatchdogProblem, 1);
    assert.equal(droneState.emergencyLanding, 0);

    assert.equal(navdata.sequenceNumber, 300711);
    assert.equal(navdata.visionFlag, 1);
  },

  'parses demo option': function() {
    var demo = parseNavdata(fixture).demo;
    assert.equal(demo.flyState, 'FLYING_OK');
    assert.equal(demo.controlState, 'CTRL_LANDED');
    assert.equal(demo.batteryPercentage, 50);

    assert.equal(demo.frontBackDegrees, 2.974);
    assert.equal(demo.leftRightDegrees, 0.55);
    assert.equal(demo.clockwiseDegrees, 1.933);

    assert.equal(demo.rotation.frontBack, 2.974);
    assert.equal(demo.rotation.leftRight, 0.55);
    assert.equal(demo.rotation.clockwise, 1.933);

    assert.equal(demo.rotation.pitch, 2.974);
    assert.equal(demo.rotation.roll, 0.55);
    assert.equal(demo.rotation.yaw, 1.933);

    assert.equal(demo.rotation.theta, 2.974);
    assert.equal(demo.rotation.phi, 0.55);
    assert.equal(demo.rotation.psi, 1.933);

    assert.equal(demo.rotation.y, 2.974);
    assert.equal(demo.rotation.x, 0.55);
    assert.equal(demo.rotation.z, 1.933);

    assert.equal(demo.altitudeMeters, 0);
    assert.equal(demo.altitude, 0);

    assert.equal(demo.xVelocity, 0.0585307739675045);
    assert.equal(demo.yVelocity, -0.8817979097366333);
    assert.equal(demo.zVelocity, 0);
    assert.equal(demo.velocity.x, 0.0585307739675045);
    assert.equal(demo.velocity.y, -0.8817979097366333);
    assert.equal(demo.velocity.z, 0);
  },

  'parses wifi option': function() {
    var wifi = parseNavdata(fixture).wifi;
    assert.equal(wifi.linkQuality, 1);
  },

  'parses time option': function() {
    var time = parseNavdata(fixture).time;
    assert.equal(time, 362979.125);
  },

  'parses rawMeasures option': function() {
    var rawMeasures = parseNavdata(fixture).rawMeasures;

    assert.equal(rawMeasures.accelerometers.x, 2040);
    assert.equal(rawMeasures.accelerometers.y, 2036);
    assert.equal(rawMeasures.accelerometers.z, 2528);

    assert.equal(rawMeasures.gyrometers.x, -23);
    assert.equal(rawMeasures.gyrometers.y, 15);
    assert.equal(rawMeasures.gyrometers.z, 0);
    assert.equal(rawMeasures.gyroscopes.x, -23);
    assert.equal(rawMeasures.gyroscopes.y, 15);
    assert.equal(rawMeasures.gyroscopes.z, 0);

    assert.equal(rawMeasures.gyrometers110[0], 0);
    assert.equal(rawMeasures.gyrometers110[1], 0);
    assert.equal(rawMeasures.gyrometers110.length, 2);
    assert.equal(rawMeasures.gyroscopes110.x, 0);
    assert.equal(rawMeasures.gyroscopes110.y, 0);

    assert.equal(rawMeasures.batteryMilliVolt, 11686);

    assert.equal(rawMeasures.usDebutEcho, 0);
    assert.equal(rawMeasures.usFinEcho, 0);
    assert.equal(rawMeasures.usAssociationEcho, 3758);
    assert.equal(rawMeasures.usDistanceEcho, 0);
    assert.equal(rawMeasures.us.echo.start, 0);
    assert.equal(rawMeasures.us.echo.end, 0);
    assert.equal(rawMeasures.us.echo.association, 3758);
    assert.equal(rawMeasures.us.echo.distance, 0);

    assert.equal(rawMeasures.usCourbeTemps, 21423);
    assert.equal(rawMeasures.usCourbeValeur, 0);
    assert.equal(rawMeasures.usCourbeRef, 120);
    assert.equal(rawMeasures.us.curve.time, 21423);
    assert.equal(rawMeasures.us.curve.value, 0);
    assert.equal(rawMeasures.us.curve.ref, 120);

    assert.equal(rawMeasures.flagEchoIni, 1);
    assert.equal(rawMeasures.nbEcho, 1);
    assert.equal(rawMeasures.sumEcho, 3539193);
    assert.equal(rawMeasures.echo.flagIni, 1);
    assert.equal(rawMeasures.echo.num, 1);
    assert.equal(rawMeasures.echo.sum, 3539193);

    assert.equal(rawMeasures.altTempRaw, 243);
    assert.equal(rawMeasures.altTemp, 243);
  },

  'parses pwm option': function() {
    var pwm = parseNavdata(fixture).pwm;

    assert.deepEqual(pwm.motors, [0, 0, 0, 0]);
    assert.deepEqual(pwm.satMotors, [255, 255, 255, 255]);
    assert.equal(pwm.gazFeedForward, 0);
    assert.equal(pwm.gazAltitude, 0);
    assert.equal(pwm.altitudeIntegral, 0);
    assert.equal(pwm.vzRef, 0);
    assert.equal(pwm.uPitch, 0);
    assert.equal(pwm.uRoll, 0);
    assert.equal(pwm.uYaw, 0);
    assert.equal(pwm.yawUI, 0);
    assert.equal(pwm.uPitchPlanif, 0);
    assert.equal(pwm.uRollPlanif, 0);
    assert.equal(pwm.uYawPlanif, 0);
    assert.equal(pwm.uGazPlanif, 0);
    assert.deepEqual(pwm.motorCurrents, [0, 0, 0, 0]);
    assert.equal(pwm.altitudeProp, 0);
    assert.equal(pwm.altitudeDer, 0);
  },

  'parses watchdog option': function() {
    var watchdog = parseNavdata(fixture).watchdog;
    assert.equal(watchdog, 4822);
  },

  'throws exception on invalid header': function() {
    assert.throws(function() {
      parseNavdata(new Buffer([1, 2, 3, 4]));
    }, /header/i);
  },

  'detects bad checksum': function() {
    // hacky way to get a copy of our fixture
    var fixtureCopy = Buffer.concat([new Buffer(0), fixture]);

    // corrupt a byte inside our fixture
    fixtureCopy[23] = fixtureCopy[23] + 1;

    assert.throws(function() {
      parseNavdata(fixtureCopy)
    }, /checksum/i);
  },

  'no infinite loop when checksum is cut off': function() {
    var incompleteFixture = Buffer.concat([
      new Buffer(0),
      fixture.slice(0, fixture.length - 8) // strip checksum from end
    ]);

    assert.throws(function() {
      parseNavdata(incompleteFixture)
    }, /beyond/i);
  },
});
<|endoftext|>


<commit_before>module.exports = [
      {
        "direct": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/moz.final.3.emailpartner_1",
        "social": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/moz.final.2.social_3",
        "title": "Privacy Lets You Be You",
        "description": "Privacy depends on encryption. Learn more about how it works, why it's essential, and why it's worth protecting.",
        "date": "February 08, 2016",
        "duration": "0:58",
        "poster": "/assets/encrypt-poster-dark-1920x1080.jpg",
        "thumbnail": "/assets/thumbnail-beat1.jpg",
        "cta" : "Will you spread the word about why privacy is important?"
      },{
        "direct": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/Mozilla_Encrypt_Beat2_landingpage",
        "social": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/Mozilla_Encrypt_Beat2_landingpage",
        "title": "Meet Encryption",
        "description": "Meet Encryption, and learn how she's hard at work to keep you safe online.",
        "date": "February 24, 2016",
        "duration": "1:16",
        "poster": "/assets/video2poster.jpg",
        "thumbnail": "/assets/thumbnail-beat2.jpg",
        "cta": "Will you spread the word about our friend, encryption?"
      },{
        "direct": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/beat4_landingpage",
        "social": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/beat4_landingpage",
        "title": "Encryption, Journalism, and Free Expression",
        "description": "Encryption doesn't just protect your privacy — it also enables free expression. Encryption helps shield journalists and their sources, allowing individuals to tell powerful stories.",
        "date": "March 9, 2016",
        "duration": "1:30",
        "poster": "/assets/video3poster.jpg",
        "thumbnail": "/assets/thumbnail-beat3.jpg",
        "cta": "Help spread the word about why encryption is worth protecting"
      },{
        "direct": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/beat5_landingpage",
        "social": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/beat5_landingpage",
        "title": "Stand Up For Strong Encryption",
        "description": "We’re seeing more and more governments attempt to undermine encryption. Will you help others understand what encryption is and stand up for strong encryption when it matters most? Sign on to Mozilla's email list and be an encryption champion.",
        "hybridDescription": "More and more governments are attempting to undermine encryption. You can help by letting us know what country you’d like to receive relevant updates about. This doesn’t sign you up for a new newsletter, but it means you may get an extra email if there’s something specific happening in that country.",
        "poster": "/assets/video4poster.jpg",
        "thumbnail": "/assets/thumbnail-beat4.jpg",
        "cta": "Thanks for signing the pledge! Can\u00A0you\u00A0help us spread the word?",
        "hybridHeader":" ",
        "duration": "0:43",
        "hybridText": "Become an encryption champion! Let us know your country preference so we can alert you to any encryption debates happening in that area."
      }
     ]
<commit_msg>update copy on cta for hybrid/4<commit_after>module.exports = [
      {
        "direct": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/moz.final.3.emailpartner_1",
        "social": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/moz.final.2.social_3",
        "title": "Privacy Lets You Be You",
        "description": "Privacy depends on encryption. Learn more about how it works, why it's essential, and why it's worth protecting.",
        "date": "February 08, 2016",
        "duration": "0:58",
        "poster": "/assets/encrypt-poster-dark-1920x1080.jpg",
        "thumbnail": "/assets/thumbnail-beat1.jpg",
        "cta" : "Will you spread the word about why privacy is important?"
      },{
        "direct": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/Mozilla_Encrypt_Beat2_landingpage",
        "social": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/Mozilla_Encrypt_Beat2_landingpage",
        "title": "Meet Encryption",
        "description": "Meet Encryption, and learn how she's hard at work to keep you safe online.",
        "date": "February 24, 2016",
        "duration": "1:16",
        "poster": "/assets/video2poster.jpg",
        "thumbnail": "/assets/thumbnail-beat2.jpg",
        "cta": "Will you spread the word about our friend, encryption?"
      },{
        "direct": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/beat4_landingpage",
        "social": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/beat4_landingpage",
        "title": "Encryption, Journalism, and Free Expression",
        "description": "Encryption doesn't just protect your privacy — it also enables free expression. Encryption helps shield journalists and their sources, allowing individuals to tell powerful stories.",
        "date": "March 9, 2016",
        "duration": "1:30",
        "poster": "/assets/video3poster.jpg",
        "thumbnail": "/assets/thumbnail-beat3.jpg",
        "cta": "Help spread the word about why encryption is worth protecting"
      },{
        "direct": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/beat5_landingpage",
        "social": "https://d24kjznqej0s8a.cloudfront.net/2016/encryption_campaign/beat5_landingpage",
        "title": "Stand Up For Strong Encryption",
        "description": "We’re seeing more and more governments attempt to undermine encryption. Will you help others understand what encryption is and stand up for strong encryption when it matters most? Sign on to Mozilla's email list and be an encryption champion.",
        "hybridDescription": "More and more governments are attempting to undermine encryption. You can help by letting us know what country you’d like to receive relevant updates about. This doesn’t sign you up for a new newsletter, but it means you may get an extra email if there’s something specific happening in that country.",
        "poster": "/assets/video4poster.jpg",
        "thumbnail": "/assets/thumbnail-beat4.jpg",
        "cta": "Thank you! Can\u00A0you\u00A0help us spread the word?",
        "hybridHeader":" ",
        "duration": "0:43",
        "hybridText": "Become an encryption champion! Let us know your country preference so we can alert you to any encryption debates happening in that area."
      }
     ]
<|endoftext|>