<commit_before>function GamePadControls()
{
    this.controlMap = [];
    this.axisMap = [];
    this.wheelTimeout = 0;
    this.wheelEvent = 0;

    this.init =
    function init()
    {
        var gamepad = new Gamepad();

        gamepad.bind(Gamepad.Event.BUTTON_DOWN, function(e)
        {
            console.log(e.control);
            if(FPSCamera.locked)
            {
                for (var i = 0; i < GamePadControls.controlMap.length; i++)
                {
                    if(GamePadControls.controlMap[i]["key"] == e.control)
                    {
                        if(GamePadControls.controlMap[i]["type"] == "press")
                        {
                            GamePadControls.controlMap[i]["call"](GamePadControls.controlMap[i]["replacement"], e);
                        }

                        if(GamePadControls.controlMap[i]["type"] != "release")
                        {
                            GamePadControls.controlMap[i]["pressed"] = true;
                        }
                    }
                }
            }
            else
            {
                FPSCamera.attachPointer();
            }
        });

        gamepad.bind(Gamepad.Event.BUTTON_UP, function(e)
        {
            if(FPSCamera.locked)
            {
                for (var i = 0; i < GamePadControls.controlMap.length; i++)
                {
                    if(GamePadControls.controlMap[i]["key"] == e.control)
                    {
                        if(GamePadControls.controlMap[i]["type"] == "release")
                        {
                            GamePadControls.controlMap[i]["call"](GamePadControls.controlMap[i]["replacement"], e);
                        }

                        if(GamePadControls.controlMap[i]["type"] != "press")
                        {
                            GamePadControls.controlMap[i]["pressed"] = false;
                        }
                    }
                }
            }
        });

        gamepad.bind(Gamepad.Event.TICK, function(gamepads)
        {
            for(var i = 0; i < 4; i++)
            {
                var value = gamepads[0].axes[i];
                value = Math.abs(value) < 0.2 ? 0 : value;
                if(FPSCamera.locked)
                {
                    for (var j = 0; j < GamePadControls.axisMap.length; j++)
                    {
                        if(GamePadControls.axisMap[j]["axis"] == i)
                        {
                            GamePadControls.axisMap[j]["value"] = value;
                        }
                    }
                }
            }
        });

        gamepad.init();
    }

    this.update =
    function update()
    {
        if(FPSCamera.locked)
        {
            for (var i = 0; i < GamePadControls.controlMap.length; i++)
            {
                if(this.controlMap[i]["pressed"])
                {
                    if(this.controlMap[i]["type"] == "hold")
                    {
                        this.controlMap[i]["call"](this.controlMap[i]["replacement"]);
                    }
                }
            }

            for (var i = 0; i < GamePadControls.axisMap.length; i++)
            {
                this.axisMap[i]["call"](this.axisMap[i]["value"]);
            }
        }
    }

    this.register =
    function register(char, replacement, callback, type)
    {
    	this.controlMap.push({"key" : char, "replacement": replacement, "call" : callback, "type" : type, "pressed" : false});
    }

    this.registerAxis =
    function registerAxis(axis, callback)
    {
    	this.axisMap.push({"axis" : axis,  "call": callback, "value": 0});
    }
}

function moveTileChooser(control, ev)
{
    FPSCamera.chooseTile(control == "up" ? 1 : -1);
}

var GamePadControls = new GamePadControls();

GamePadControls.register("FACE_1", " ", FPSCamera.fly, "hold");
GamePadControls.registerAxis("0", function(value)
{
    FPSCamera.moveFromForce(0, value);
});
GamePadControls.registerAxis("1", function(value)
{
    FPSCamera.moveFromForce(-value, 0);
});
GamePadControls.registerAxis("2", function(value)
{
    FPSCamera.cameraYaw -= value * FPSCamera.sensitivity * 16;
});
GamePadControls.registerAxis("3", function(value)
{
    FPSCamera.cameraPitch -= value * FPSCamera.sensitivity * 16;
});

GamePadControls.register("LEFT_TOP_SHOULDER", "up", moveTileChooser, "press");
GamePadControls.register("RIGHT_TOP_SHOULDER", "down", moveTileChooser, "press");

GamePadControls.register("LEFT_BOTTOM_SHOULDER", "mouse-0", FPSCamera.placeTile, "press");
GamePadControls.register("RIGHT_BOTTOM_SHOULDER", "mouse-2", FPSCamera.placeTile, "press");

GamePadControls.register("LEFT_STICK", "shift", FPSCamera.fly, "hold");
GamePadControls.register("RIGHT_STICK", "gamepack_stick", FPSCamera.pickTile, "press");
<commit_msg>Removed logs<commit_after>function GamePadControls()
{
    this.controlMap = [];
    this.axisMap = [];
    this.wheelTimeout = 0;
    this.wheelEvent = 0;

    this.init =
    function init()
    {
        var gamepad = new Gamepad();

        gamepad.bind(Gamepad.Event.BUTTON_DOWN, function(e)
        {
            if(FPSCamera.locked)
            {
                for (var i = 0; i < GamePadControls.controlMap.length; i++)
                {
                    if(GamePadControls.controlMap[i]["key"] == e.control)
                    {
                        if(GamePadControls.controlMap[i]["type"] == "press")
                        {
                            GamePadControls.controlMap[i]["call"](GamePadControls.controlMap[i]["replacement"], e);
                        }

                        if(GamePadControls.controlMap[i]["type"] != "release")
                        {
                            GamePadControls.controlMap[i]["pressed"] = true;
                        }
                    }
                }
            }
            else
            {
                FPSCamera.attachPointer();
            }
        });

        gamepad.bind(Gamepad.Event.BUTTON_UP, function(e)
        {
            if(FPSCamera.locked)
            {
                for (var i = 0; i < GamePadControls.controlMap.length; i++)
                {
                    if(GamePadControls.controlMap[i]["key"] == e.control)
                    {
                        if(GamePadControls.controlMap[i]["type"] == "release")
                        {
                            GamePadControls.controlMap[i]["call"](GamePadControls.controlMap[i]["replacement"], e);
                        }

                        if(GamePadControls.controlMap[i]["type"] != "press")
                        {
                            GamePadControls.controlMap[i]["pressed"] = false;
                        }
                    }
                }
            }
        });

        gamepad.bind(Gamepad.Event.TICK, function(gamepads)
        {
            for(var i = 0; i < 4; i++)
            {
                var value = gamepads[0].axes[i];
                value = Math.abs(value) < 0.2 ? 0 : value;
                if(FPSCamera.locked)
                {
                    for (var j = 0; j < GamePadControls.axisMap.length; j++)
                    {
                        if(GamePadControls.axisMap[j]["axis"] == i)
                        {
                            GamePadControls.axisMap[j]["value"] = value;
                        }
                    }
                }
            }
        });

        gamepad.init();
    }

    this.update =
    function update()
    {
        if(FPSCamera.locked)
        {
            for (var i = 0; i < GamePadControls.controlMap.length; i++)
            {
                if(this.controlMap[i]["pressed"])
                {
                    if(this.controlMap[i]["type"] == "hold")
                    {
                        this.controlMap[i]["call"](this.controlMap[i]["replacement"]);
                    }
                }
            }

            for (var i = 0; i < GamePadControls.axisMap.length; i++)
            {
                this.axisMap[i]["call"](this.axisMap[i]["value"]);
            }
        }
    }

    this.register =
    function register(char, replacement, callback, type)
    {
    	this.controlMap.push({"key" : char, "replacement": replacement, "call" : callback, "type" : type, "pressed" : false});
    }

    this.registerAxis =
    function registerAxis(axis, callback)
    {
    	this.axisMap.push({"axis" : axis,  "call": callback, "value": 0});
    }
}

function moveTileChooser(control, ev)
{
    FPSCamera.chooseTile(control == "up" ? 1 : -1);
}

var GamePadControls = new GamePadControls();

GamePadControls.register("FACE_1", " ", FPSCamera.fly, "hold");
GamePadControls.registerAxis("0", function(value)
{
    FPSCamera.moveFromForce(0, value);
});
GamePadControls.registerAxis("1", function(value)
{
    FPSCamera.moveFromForce(-value, 0);
});
GamePadControls.registerAxis("2", function(value)
{
    FPSCamera.cameraYaw -= value * FPSCamera.sensitivity * 16;
});
GamePadControls.registerAxis("3", function(value)
{
    FPSCamera.cameraPitch -= value * FPSCamera.sensitivity * 16;
});

GamePadControls.register("LEFT_TOP_SHOULDER", "up", moveTileChooser, "press");
GamePadControls.register("RIGHT_TOP_SHOULDER", "down", moveTileChooser, "press");

GamePadControls.register("LEFT_BOTTOM_SHOULDER", "mouse-0", FPSCamera.placeTile, "press");
GamePadControls.register("RIGHT_BOTTOM_SHOULDER", "mouse-2", FPSCamera.placeTile, "press");

GamePadControls.register("LEFT_STICK", "shift", FPSCamera.fly, "hold");
GamePadControls.register("RIGHT_STICK", "gamepack_stick", FPSCamera.pickTile, "press");
<|endoftext|>


<commit_before>/*
 * Copyright 2015-2016 Red Hat, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags.
 *
 * 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.
 */

import fetch from 'node-fetch';

const CONSOLE_RECORDER = Symbol('CONSOLE_RECORDER');
const HTTP_OPTIONS = Symbol('HTTP_OPTIONS');

class NoOpRecorder {
    record() {
    }
}

class ConsoleRecorder extends NoOpRecorder {
    record(span) {
        console.log(JSON.stringify({
            traceId: span.getTraceId(),
            parentId: span.getParentId(),
            spanId: span.getId(),

            operationName: span.getOperationName(),
            startTime: span.getStartTime(),
            duration: span.getDuration(),
            logs: span.getLogs(),
            tags: span.getTags(),
        }));
    }
}

class HttpRecorder {
    constructor(url, username, password, debug) {
        function auth(name, pass) {
            const namePass = `${name}:${pass}`;
            return `Basic ${new Buffer(namePass).toString('base64')}`;
        }

        this[HTTP_OPTIONS] = {
            endpoint: `${url}/hawkular/apm/traces/fragments`,
            headers: {
                Authorization: auth(username, password),
                'Content-Type': 'application/json',
            },
        };

        if (debug) {
            this[CONSOLE_RECORDER] = new ConsoleRecorder();
        }
    }

    record(span) {
        if (!span) {
            return;
        }

        const trace = span.context().getTrace().fromSpan(span);

        if (this[CONSOLE_RECORDER]) {
            this[CONSOLE_RECORDER].record(span);
        }

        fetch(this[HTTP_OPTIONS].endpoint, {
            method: 'POST',
            body: JSON.stringify([trace]),
            headers: this[HTTP_OPTIONS].headers,
        }).catch((err) => {
            console.log(`Error when reporting trace! Error: ${err}`);
        });
    }
}

module.exports = {
    NoOpRecorder,
    ConsoleRecorder,
    HttpRecorder,
};
<commit_msg>HWKAPM-798 | HTTP recorder allow custom options<commit_after>/*
 * Copyright 2015-2016 Red Hat, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags.
 *
 * 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.
 */

import fetch from 'node-fetch';

const CONSOLE_RECORDER = Symbol('CONSOLE_RECORDER');
const AUTHORIZATION = Symbol('AUTHORIZATION');
const ENDPOINT = Symbol('ENDPOINT');
const TIMEOUT = Symbol('TIMEOUT');

class NoOpRecorder {
    record() {
    }
}

class ConsoleRecorder extends NoOpRecorder {
    record(span) {
        console.log(JSON.stringify({
            traceId: span.getTraceId(),
            parentId: span.getParentId(),
            spanId: span.getId(),

            operationName: span.getOperationName(),
            startTime: span.getStartTime(),
            duration: span.getDuration(),
            logs: span.getLogs(),
            tags: span.getTags(),
        }));
    }
}

class HttpRecorder {
    constructor(url, username, password, debug = false, timeout = 0) {
        function auth(name, pass) {
            const namePass = `${name}:${pass}`;
            return `Basic ${new Buffer(namePass).toString('base64')}`;
        }

        this[AUTHORIZATION] = auth(username, password);
        this[ENDPOINT] = `${url}/hawkular/apm/traces/fragments`;
        this[TIMEOUT] = timeout;

        if (debug) {
            this[CONSOLE_RECORDER] = new ConsoleRecorder();
        }
    }

    record(span) {
        if (!span) {
            return;
        }

        const trace = span.context().getTrace().fromSpan(span);

        if (this[CONSOLE_RECORDER]) {
            this[CONSOLE_RECORDER].record(span);
        }

        fetch(this[ENDPOINT], {
            method: 'POST',
            body: JSON.stringify([trace]),
            timeout: this[TIMEOUT],
            headers: {
                'Content-Type': 'application/json',
                Authorization: this[AUTHORIZATION],
            },
        }).then((response) => {
            if (response.status !== 204) {
                console.log(`Server did not accept trace data: ${response}`);
            }
        }).catch((err) => {
            console.log(`Error when reporting trace! Error: ${err}`);
        });
    }
}

module.exports = {
    NoOpRecorder,
    ConsoleRecorder,
    HttpRecorder,
};
<|endoftext|>


<commit_before>var path = require('path')
var fs = require('fs')
var node = process.execPath
var spawn = require('child_process').spawn
var shop = require('../index.js')
var concat = require('concat-stream')
var rimraf = require('rimraf')
var mkdirp = require('mkdirp')
var semver = require('semver')

var pidfile = path.resolve(shop.datadir, 'registry.pid')
var logfile = path.resolve(shop.datadir, 'registry.log')
var assetdir = path.resolve(shop.datadir, 'registry')

if (require.main === module) {
  switch (process.argv[2]) {
    case 'daemon':
      daemon(process.argv.slice(3))
      break
    case 'kill':
      kill()
      break
  }
}

exports.kill = kill
function kill () {
  try {
    var pid = +fs.readFileSync(pidfile, 'ascii').trim()
    process.kill(pid, 'SIGKILL')
  } catch (er) {}
  try {
    fs.unlinkSync(pidfile)
  } catch (er) {}
}

exports.run = run
function run (args) {
  kill()

  var child = spawn(node, [__filename, 'daemon'].concat(args || []), {
    stdio: 'ignore',
    detached: true,
    cwd: path.resolve(__dirname, '..')
  })
  child.unref()
}

exports.daemon = daemon
function daemon (args) {
  kill()

  var fd = fs.openSync(pidfile, 'wx')
  fs.writeSync(fd, process.pid + '\n')
  fs.closeSync(fd)

  var http = require('http')
  var lesson = args[0]
  var server = http.createServer(handler(args))
  server.listen(15443, function () {
    console.log('%s Listening', lesson)
  })
}

function handler (args) {
  var lesson = args[0]
  return function (req, res) {
    console.log('%s %s %s', lesson, req.method, req.url)

    switch (lesson) {
      case 'install-a-module':
        if (req.url.match(/^\/(wrappy|once)/)) {
          static(req, res)
          return
        }
        break

      case 'login':
        if (req.url.match(/^\/-\/user\/org.couchdb.user:/)) {
          if (req.method === 'PUT') {
            res.statusCode = 201
            return res.end(JSON.stringify({ ok: 'created' }))
          }
        }
        break

      case 'publish':
        if (req.method === 'PUT')
          return receivePublishPut(req, res)
        else
          return static(req, res)

      case 'dist-tag':
        console.log('dist-tag', req.method, req.url)
        if (req.url.match(/^\/-\/package\/[^\/]+\/dist-tags/))
          return handleDistTags(req, res)
        else
          return static(req, res)

        break
    }

    _404(req, res)
  }
}

function handleDistTags (req, res) {
  console.log('dist-tag', req.method, req.url)

  var parsed = req.url.match(/^\/-\/package\/([^\/]+)\/dist-tags(?:\/(.*))?/)
  var urlName = parsed[1]
  var tag = parsed[2]
  var dataFile = path.resolve(assetdir, urlName, 'body.json')

  if (!fs.existsSync(dataFile))
    return _404(req, res)

  var data = require(dataFile)

  if (req.method === 'GET' || req.method === 'HEAD') {
    return res.end(JSON.stringify(data['dist-tags']))
  }

  if (req.method === 'POST' || req.method === 'PUT') {
    req.setEncoding('utf8')
    return req.pipe(concat(function (body) {
      console.error(body)
      body = JSON.parse(body)
      if (tag) {
        if (semver.valid(body))
          data['dist-tags'][tag] = body
        else
          return _403(req, res, { error: 'invalid version' })
      } else if (body && typeof body === 'object' && !tag) {
        if (req.method === 'PUT') {
          data['dist-tags'] = body
        } else for (var dt in body) {
          data['dist-tags'][dt] = body[dt]
        }
      }

      for (var dt in data['dist-tags']) {
        if (semver.valid(dt)) {
          return _403(req, res, {
            error: 'You may not have a dist tag that is also a valid version'
          })
        }
        var ver = data['dist-tags'][dt]
        if (!data.versions[ver]) {
          return _403(req, res, {
            error: 'Tag points at invalid version: ' + dt + ' -> ' + ver
          })
        }
      }

      try {
        fs.writeFileSync(dataFile, JSON.stringify(data))
      } catch (er) {
        return _500(req, res, er)
      }
      res.statusCode = 201
      res.end(JSON.stringify({ok: 'created'}))
    }))
  }

  if (req.method === 'DELETE') {
    if (!tag) {
      return _403(req, res, {
        error: 'You must provide a tag to delete'
      })
    }
    console.log('delete a tag', req.url)
    delete data['dist-tags'][tag]
    try {
      fs.writeFileSync(dataFile, JSON.stringify(data))
    } catch (er) {
      return _500(req, res, er)
    }
    res.statusCode = 201
    return res.end(JSON.stringify({ok: 'deleted'}))
  }

  return _404(req, res)
}

function receivePublishPut (req, res) {
  req.setEncoding('utf8')
  req.pipe(concat(next))

  function next(data) {
    data = JSON.parse(data)
    var ver = Object.keys(data.versions)[0]
    var tgzBase = data._id + '-' + ver +'.tgz'
    var att = data._attachments[tgzBase]
    att = att && att.data
    var dir = path.resolve(assetdir, data._id)
    var jsonFile = path.resolve(dir, 'body.json')
    var tgzDir = path.resolve(dir, '-')
    var tgzFile = path.resolve(tgzDir, tgzBase)

    if (!att) {
      return _403(req, res, {
        message: 'no tarball attachment'
      })
    }

    if (!data._id) {
      return _403(req, res, {
        message: 'invalid package name'
      })
    }

    try {
      var existing = JSON.parse(fs.readFileSync(jsonFile, 'utf8'))
    } catch (er) {
      existing = {}
    }

    mkdirp.sync(tgzDir)

    if (existing && existing.time && existing.time[ver]) {
      return _403(req, res, {
        message: 'You cannot publish the same version more than once\n' +
          'Bump the version number in package.json, and try again.'
      })
    }

    existing.versions = existing.versions || {}
    existing.versions[ver] = data.versions[ver]

    for (var i in data) {
      if (i !== 'versions' && i !== '_attachments') {
        existing[i] = data[i]
      }
    }

    delete existing._attachments

    existing.time = existing.time || {}
    var now = new Date().toISOString()
    existing.time[ver] = now
    existing.time.created = existing.time.created || now
    existing.time.modified = now

    try {
      fs.writeFileSync(tgzFile, att, { encoding: 'base64' })
    } catch (er) {
      return _500(req, res, er)
    }

    try {
      fs.writeFileSync(jsonFile, JSON.stringify(existing))
    } catch (er) {
      return _500(req, res, er)
    }

    res.statusCode = 201
    res.end(JSON.stringify({ok: 'created'}))
  }
}

function _404 (req, res) {
  res.statusCode = 404
  res.setHeader('content-type', 'application/json')
  res.end(JSON.stringify({error: 'missing'}))
}

function _403 (req, res, er) {
  res.statusCode = 403
  res.setHeader('content-type', 'application/json')
  res.end(JSON.stringify({error: er.stack || er.message}))
}

function _500 (req, res, er) {
  if (er.code === 'ENOENT')
    return _404(req, res)

  res.statusCode = 500
  res.setHeader('content-type', 'application/json')
  res.end(JSON.stringify({error: er.stack || er.message}))
}

function static (req, res) {
  if (req.method !== 'GET' && req.url !== 'HEAD')
    return _404(req, res)

  if (req.url.match(/\.\./))
    return _404(req, res)

  var file = path.join(assetdir, req.url)

  var isJson = false
  try {
    var body = fs.readFileSync(file)
  } catch (er) {
    try {
      var body = fs.readFileSync(path.resolve(file, 'body.json'))
      var isJson = true
    } catch (er) {
      return _500(req, res, er)
    }
  }

  res.setHeader('content-length', body.length)
  if (isJson)
    res.setHeader('content-type', 'application/json')
  res.setHeader('connection', 'close')
  res.end(body)
}
<commit_msg>Simply registry by always allowing static() handling for GETs<commit_after>var path = require('path')
var fs = require('fs')
var node = process.execPath
var spawn = require('child_process').spawn
var shop = require('../index.js')
var concat = require('concat-stream')
var rimraf = require('rimraf')
var mkdirp = require('mkdirp')
var semver = require('semver')

var pidfile = path.resolve(shop.datadir, 'registry.pid')
var logfile = path.resolve(shop.datadir, 'registry.log')
var assetdir = path.resolve(shop.datadir, 'registry')

if (require.main === module) {
  switch (process.argv[2]) {
    case 'daemon':
      daemon(process.argv.slice(3))
      break
    case 'kill':
      kill()
      break
  }
}

exports.kill = kill
function kill () {
  try {
    var pid = +fs.readFileSync(pidfile, 'ascii').trim()
    process.kill(pid, 'SIGKILL')
  } catch (er) {}
  try {
    fs.unlinkSync(pidfile)
  } catch (er) {}
}

exports.run = run
function run (args) {
  kill()

  var child = spawn(node, [__filename, 'daemon'].concat(args || []), {
    stdio: 'ignore',
    detached: true,
    cwd: path.resolve(__dirname, '..')
  })
  child.unref()
}

exports.daemon = daemon
function daemon (args) {
  kill()

  var fd = fs.openSync(pidfile, 'wx')
  fs.writeSync(fd, process.pid + '\n')
  fs.closeSync(fd)

  var http = require('http')
  var lesson = args[0]
  var server = http.createServer(handler(args))
  server.listen(15443, function () {
    console.log('%s Listening', lesson)
  })
}

function handler (args) {
  var lesson = args[0]
  return function (req, res) {
    console.log('%s %s %s', lesson, req.method, req.url)

    switch (lesson) {
      case 'install-a-module':
        // Just the default static handling
        break

      case 'login':
        if (req.url.match(/^\/-\/user\/org.couchdb.user:/)) {
          if (req.method === 'PUT') {
            res.statusCode = 201
            return res.end(JSON.stringify({ ok: 'created' }))
          }
        }
        break

      case 'publish':
        if (req.method === 'PUT')
          return receivePublishPut(req, res)
        break

      case 'dist-tag':
        if (req.url.match(/^\/-\/package\/[^\/]+\/dist-tags/))
          return handleDistTags(req, res)
        break
    }

    static(req, res)
  }
}

function handleDistTags (req, res) {
  console.log('dist-tag', req.method, req.url)

  var parsed = req.url.match(/^\/-\/package\/([^\/]+)\/dist-tags(?:\/(.*))?/)
  var urlName = parsed[1]
  var tag = parsed[2]
  var dataFile = path.resolve(assetdir, urlName, 'body.json')

  if (!fs.existsSync(dataFile))
    return _404(req, res)

  var data = require(dataFile)

  if (req.method === 'GET' || req.method === 'HEAD') {
    return res.end(JSON.stringify(data['dist-tags']))
  }

  if (req.method === 'POST' || req.method === 'PUT') {
    req.setEncoding('utf8')
    return req.pipe(concat(function (body) {
      console.error(body)
      body = JSON.parse(body)
      if (tag) {
        if (semver.valid(body))
          data['dist-tags'][tag] = body
        else
          return _403(req, res, { error: 'invalid version' })
      } else if (body && typeof body === 'object' && !tag) {
        if (req.method === 'PUT') {
          data['dist-tags'] = body
        } else for (var dt in body) {
          data['dist-tags'][dt] = body[dt]
        }
      }

      for (var dt in data['dist-tags']) {
        if (semver.valid(dt)) {
          return _403(req, res, {
            error: 'You may not have a dist tag that is also a valid version'
          })
        }
        var ver = data['dist-tags'][dt]
        if (!data.versions[ver]) {
          return _403(req, res, {
            error: 'Tag points at invalid version: ' + dt + ' -> ' + ver
          })
        }
      }

      try {
        fs.writeFileSync(dataFile, JSON.stringify(data))
      } catch (er) {
        return _500(req, res, er)
      }
      res.statusCode = 201
      res.end(JSON.stringify({ok: 'created'}))
    }))
  }

  if (req.method === 'DELETE') {
    if (!tag) {
      return _403(req, res, {
        error: 'You must provide a tag to delete'
      })
    }
    console.log('delete a tag', req.url)
    delete data['dist-tags'][tag]
    try {
      fs.writeFileSync(dataFile, JSON.stringify(data))
    } catch (er) {
      return _500(req, res, er)
    }
    res.statusCode = 201
    return res.end(JSON.stringify({ok: 'deleted'}))
  }

  return _404(req, res)
}

function receivePublishPut (req, res) {
  req.setEncoding('utf8')
  req.pipe(concat(next))

  function next(data) {
    data = JSON.parse(data)
    var ver = Object.keys(data.versions)[0]
    var tgzBase = data._id + '-' + ver +'.tgz'
    var att = data._attachments[tgzBase]
    att = att && att.data
    var dir = path.resolve(assetdir, data._id)
    var jsonFile = path.resolve(dir, 'body.json')
    var tgzDir = path.resolve(dir, '-')
    var tgzFile = path.resolve(tgzDir, tgzBase)

    if (!att) {
      return _403(req, res, {
        message: 'no tarball attachment'
      })
    }

    if (!data._id) {
      return _403(req, res, {
        message: 'invalid package name'
      })
    }

    try {
      var existing = JSON.parse(fs.readFileSync(jsonFile, 'utf8'))
    } catch (er) {
      existing = {}
    }

    mkdirp.sync(tgzDir)

    if (existing && existing.time && existing.time[ver]) {
      return _403(req, res, {
        message: 'You cannot publish the same version more than once\n' +
          'Bump the version number in package.json, and try again.'
      })
    }

    existing.versions = existing.versions || {}
    existing.versions[ver] = data.versions[ver]

    for (var i in data) {
      if (i !== 'versions' && i !== '_attachments') {
        existing[i] = data[i]
      }
    }

    delete existing._attachments

    existing.time = existing.time || {}
    var now = new Date().toISOString()
    existing.time[ver] = now
    existing.time.created = existing.time.created || now
    existing.time.modified = now

    try {
      fs.writeFileSync(tgzFile, att, { encoding: 'base64' })
    } catch (er) {
      return _500(req, res, er)
    }

    try {
      fs.writeFileSync(jsonFile, JSON.stringify(existing))
    } catch (er) {
      return _500(req, res, er)
    }

    res.statusCode = 201
    res.end(JSON.stringify({ok: 'created'}))
  }
}

function _404 (req, res) {
  res.statusCode = 404
  res.setHeader('content-type', 'application/json')
  res.end(JSON.stringify({error: 'missing'}))
}

function _403 (req, res, er) {
  res.statusCode = 403
  res.setHeader('content-type', 'application/json')
  res.end(JSON.stringify({error: er.stack || er.message}))
}

function _500 (req, res, er) {
  if (er.code === 'ENOENT')
    return _404(req, res)

  res.statusCode = 500
  res.setHeader('content-type', 'application/json')
  res.end(JSON.stringify({error: er.stack || er.message}))
}

function static (req, res) {
  if (req.method !== 'GET' && req.url !== 'HEAD')
    return _404(req, res)

  if (req.url.match(/\.\./))
    return _404(req, res)

  var file = path.join(assetdir, req.url)

  var isJson = false
  try {
    var body = fs.readFileSync(file)
  } catch (er) {
    try {
      var body = fs.readFileSync(path.resolve(file, 'body.json'))
      var isJson = true
    } catch (er) {
      return _500(req, res, er)
    }
  }

  res.setHeader('content-length', body.length)
  if (isJson)
    res.setHeader('content-type', 'application/json')
  res.setHeader('connection', 'close')
  res.end(body)
}
<|endoftext|>


<commit_before>/**
 * JSON-WS Service Registry
 */

'use strict';

const fs = require('fs');
const ejs = require('ejs');
const path = require('path');
const ujs = require('uglify-js');
const url = require('url');
const crypto = require('crypto');
const express = require('express');

const getLanguageProxy = require('./get-language-proxy');
const Trace = require('./trace');

function uglify(script, level) {
	if (level !== 'none') {
		let ast = ujs.parse(script);
		ast.figure_out_scope();
		if (level == 'fast') {
			ast = ast.transform(ujs.Compressor({ // eslint-disable-line new-cap
				conditionals  : false,
				comparisons   : false,
				warnings      : false
			}, false));
		} else {
			ast = ast.transform(ujs.Compressor({warnings: false})); // eslint-disable-line new-cap
		}
		ast.figure_out_scope();
		if (level !== 'fast') {
			ast.compute_char_frequency();
		}
		ast.mangle_names();
		return ast.print_to_string();
	} else {
		return script;
	}
}

class ServiceRegistry {
	constructor(options) {
		this.rootPath = options.rootPath.replace(/\/*$/gi, '');
		this.httpServer = options.httpServer;
		this.expressApp = options.expressApp;
		this.trace = new Trace(options.logger);
		this.services = new Map(/*serviceRootPrefix:Service*/);
		this.transports = new Set(/*Transport*/);
		this.router = express.Router({caseSensitive: true}); // eslint-disable-line new-cap
		this.router.use((req, res, next) => { this._routeHandler(req, res, next) });
	}

	attachExpressRouter() {
		this.expressApp.all(`${this.rootPath}/*`, (req, res, next) => {
			req.originalParams = req.params;
			next();
		});
		this.expressApp.use(this.rootPath, this.router);
	}

	addService(service) {
		const rootPrefix = `/${service.name}/${service.version}`;
		this.services.set(rootPrefix, service);
		return rootPrefix;
	}

	addTransport(transportConstructor) {
		const transportInstance = new transportConstructor(this);
		this.transports.add(transportInstance);
		return transportInstance;
	}

	_renderMetadataPage(service, req, res) {
		if (req.query.json !== undefined) {
			// Handle the "?json" query, return an API description in JSON format
			res.writeHead(200, {'Content-Type': 'application/json'});
			res.write(JSON.stringify(service.metadata));
			res.end();
			return;
		}

		if (req.query.proxy !== undefined) {
			// Generate a proxy file
			let signature = service.timeStamp;
			['proxy', 'namespace', 'localName'].forEach(param => {
				if (req.query.hasOwnProperty(param)) {
					signature += ';' + req.query[param];
				}
			});

			const sha1 = crypto.createHash('sha1');
			sha1.update(signature);
			const etag = sha1.digest('hex');
			if (req.headers['if-none-match'] === etag) {
				res.status(304).end();
				return;
			}

			// Request for a proxy generator
			getLanguageProxy({
				serviceInstance: service,
				language: req.query.proxy,
				localName: req.query.setNamespace || req.query.localName
			}).then(function (html) {
				let contentType = 'text/plain';
				const proxyType = req.query.proxy.toLowerCase();
				if (proxyType.indexOf('javascript') != -1) {
					contentType = 'application/javascript';
					html = uglify(html, 'full');
				} else if (proxyType.indexOf('java') != -1) {
					contentType = 'text/java';
				}
				res.writeHead(200, {
					'Content-Type': contentType,
					'Content-Length': html.length,
					'Cache-Control': 'must-revalidate',
					'ETag': etag
				});
				res.write(html);
				res.end();
			}).catch(function (err) {
				if (err.code === 'ENOENT') {
					res.status(404).send('Proxy generator not found.');
				} else {
					res.status(500).send(err.toString());
				}
				res.end();
			});
		} else if (req.query.viewer !== undefined) {
			// Render viewer/experiment page
			const examples = [];
			// TODO: update these loops when methodMap and snippetMap become maps:
			for (const method in service.methodMap) {
				if (service.methodMap[method].examples['JavaScript']) {
					examples.push(method);
				}
			}
			const snippets = [];
			for (const snippet in service.snippetMap) {
				if (service.snippetMap[snippet]['JavaScript']) {
					snippets.push(snippet);
				}
			}
			let code = null;
			let name = req.query.example;
			if (examples.indexOf(name) > -1) {
				code = service.methodMap[name].examples['JavaScript'];
			} else {
				name = req.query.snippet;
				if (snippets.indexOf(name) > -1) {
					code = service.snippetMap[name]['JavaScript'];
				}
			}
			ejs.renderFile(__dirname + '/../templates/viewer.ejs', {
				root: this.rootPath,
				title: `${service.name} ${service.version}`,
				url: service.path,
				examples,
				snippets,
				code
			}, { _with: false }, function (err, file) {
				res.send(err ? err.toString() : file);
			});
		} else {
			// Find out list of proxy generators
			fs.readdir(__dirname + '/../proxies/', (err, files) => {
				const proxies = files.filter(f => f.endsWith('.ejs')).map(f => f.substr(0, f.length - 4));

				// Render the metadata template
				ejs.renderFile(
					__dirname + '/../templates/metadata.ejs',
					{
						metadata: service,
						ejs,
						ujs,
						proxies,
						root: this.rootPath
					}, {_with: false}, (err, html) => {
						if (err) {
							res.status(500).send(err.toString());
						} else {
							res.send(html);
						}
					});
			});
		}
	}

	_routeHandler(req, res, next) {
		if (req.method.toUpperCase() == 'OPTIONS') {
			res.set('Access-Control-Allow-Origin', '*');
			res.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
			res.set('Access-Control-Max-Age', 1000);
			res.set('Access-Control-Allow-Headers', 'origin, x-csrftoken, content-type, accept');
			res.end();
			return;
		}

		const requestUrl = url.parse(req.path).pathname || '';
		if (requestUrl === '/') {
			ejs.renderFile(__dirname + '/../templates/services.ejs', {
				services: Array.from(this.services.entries()).map(entry => ({
					path: `${req.baseUrl}${entry[0]}`,
					metadata: entry[1]
				}))
			}, {_with: false}, function (err, html) {
				if (err) {
					res.status(500).send(err.toString());
				} else {
					res.set('Content-Type', 'text/html');
					res.set('Content-Length', html.length);
					res.set('Cache-Control', 'must-revalidate');
					res.send(html);
				}
			});
		} else if (requestUrl == '/client' || requestUrl == '/client.js') {
			fs.readFile(__dirname + '/../lib/client.js', {encoding: 'utf-8'}, function (err, text) {
				if (err) res.status(404).send('Error reading client library\n\n' + err);
				else {
					res.set('Content-Type', 'application/javascript');
					res.send(uglify(text, 'full'));
				}
			});
		} else if (requestUrl.indexOf('/static/') != -1) {
			const fileName = requestUrl.substring(requestUrl.lastIndexOf('/') + 1);

			if (fileName.indexOf('.js') != -1) {
				res.set('Content-Type', 'application/javascript');
			} else if (fileName.indexOf('.css') != -1) {
				res.set('Content-Type', 'text/css');
			}
			res.sendFile(path.resolve(__dirname + '/../static/' + fileName));
		} else {
			const serviceInstance = this.getService(requestUrl);
			if (serviceInstance && req.method.toUpperCase() == 'GET') {
				this._renderMetadataPage(serviceInstance, req, res);
			} else {
				next();
			}
		}
	}

	getService(pathPrefix) {
		return this.services.get(pathPrefix);
	}
}

const registries = new Map(); // rootPath: string -> registry: ServiceRegistry

/**
 * API Registry middleware for Express/Connect
 * Responds to OPTIONS request, renders a page listing all registered services
 * and serves the NodeJS/browser client library.
 * @param {String} rootPath Mount point of the service registry.
 */
module.exports = function(options) {
	let registry = registries.get(options.rootPath);
	if (!registry) {
		registry = new ServiceRegistry(options);
		registries.set(options.rootPath, registry);
	}
	return registry;
};
<commit_msg>Use major.minor for the version in the service URL<commit_after>/**
 * JSON-WS Service Registry
 */

'use strict';

const fs = require('fs');
const ejs = require('ejs');
const path = require('path');
const ujs = require('uglify-js');
const url = require('url');
const crypto = require('crypto');
const express = require('express');
const semver = require('semver');

const getLanguageProxy = require('./get-language-proxy');
const Trace = require('./trace');

function uglify(script, level) {
	if (level !== 'none') {
		let ast = ujs.parse(script);
		ast.figure_out_scope();
		if (level == 'fast') {
			ast = ast.transform(ujs.Compressor({ // eslint-disable-line new-cap
				conditionals  : false,
				comparisons   : false,
				warnings      : false
			}, false));
		} else {
			ast = ast.transform(ujs.Compressor({warnings: false})); // eslint-disable-line new-cap
		}
		ast.figure_out_scope();
		if (level !== 'fast') {
			ast.compute_char_frequency();
		}
		ast.mangle_names();
		return ast.print_to_string();
	} else {
		return script;
	}
}

class ServiceRegistry {
	constructor(options) {
		this.rootPath = options.rootPath.replace(/\/*$/gi, '');
		this.httpServer = options.httpServer;
		this.expressApp = options.expressApp;
		this.trace = new Trace(options.logger);
		this.services = new Map(/*serviceRootPrefix:Service*/);
		this.transports = new Set(/*Transport*/);
		this.router = express.Router({caseSensitive: true}); // eslint-disable-line new-cap
		this.router.use((req, res, next) => { this._routeHandler(req, res, next) });
	}

	attachExpressRouter() {
		this.expressApp.all(`${this.rootPath}/*`, (req, res, next) => {
			req.originalParams = req.params;
			next();
		});
		this.expressApp.use(this.rootPath, this.router);
	}

	addService(service) {
		const version = semver(service.version);
		const rootPrefix = `/${service.name}/${version.major}.${version.minor}`;
		this.services.set(rootPrefix, service);
		return rootPrefix;
	}

	addTransport(transportConstructor) {
		const transportInstance = new transportConstructor(this);
		this.transports.add(transportInstance);
		return transportInstance;
	}

	_renderMetadataPage(service, req, res) {
		if (req.query.json !== undefined) {
			// Handle the "?json" query, return an API description in JSON format
			res.writeHead(200, {'Content-Type': 'application/json'});
			res.write(JSON.stringify(service.metadata));
			res.end();
			return;
		}

		if (req.query.proxy !== undefined) {
			// Generate a proxy file
			let signature = service.timeStamp;
			['proxy', 'namespace', 'localName'].forEach(param => {
				if (req.query.hasOwnProperty(param)) {
					signature += ';' + req.query[param];
				}
			});

			const sha1 = crypto.createHash('sha1');
			sha1.update(signature);
			const etag = sha1.digest('hex');
			if (req.headers['if-none-match'] === etag) {
				res.status(304).end();
				return;
			}

			// Request for a proxy generator
			getLanguageProxy({
				serviceInstance: service,
				language: req.query.proxy,
				localName: req.query.setNamespace || req.query.localName
			}).then(function (html) {
				let contentType = 'text/plain';
				const proxyType = req.query.proxy.toLowerCase();
				if (proxyType.indexOf('javascript') != -1) {
					contentType = 'application/javascript';
					html = uglify(html, 'full');
				} else if (proxyType.indexOf('java') != -1) {
					contentType = 'text/java';
				}
				res.writeHead(200, {
					'Content-Type': contentType,
					'Content-Length': html.length,
					'Cache-Control': 'must-revalidate',
					'ETag': etag
				});
				res.write(html);
				res.end();
			}).catch(function (err) {
				if (err.code === 'ENOENT') {
					res.status(404).send('Proxy generator not found.');
				} else {
					res.status(500).send(err.toString());
				}
				res.end();
			});
		} else if (req.query.viewer !== undefined) {
			// Render viewer/experiment page
			const examples = [];
			// TODO: update these loops when methodMap and snippetMap become maps:
			for (const method in service.methodMap) {
				if (service.methodMap[method].examples['JavaScript']) {
					examples.push(method);
				}
			}
			const snippets = [];
			for (const snippet in service.snippetMap) {
				if (service.snippetMap[snippet]['JavaScript']) {
					snippets.push(snippet);
				}
			}
			let code = null;
			let name = req.query.example;
			if (examples.indexOf(name) > -1) {
				code = service.methodMap[name].examples['JavaScript'];
			} else {
				name = req.query.snippet;
				if (snippets.indexOf(name) > -1) {
					code = service.snippetMap[name]['JavaScript'];
				}
			}
			ejs.renderFile(__dirname + '/../templates/viewer.ejs', {
				root: this.rootPath,
				title: `${service.name} ${service.version.raw}`,
				url: service.path,
				examples,
				snippets,
				code
			}, { _with: false }, function (err, file) {
				res.send(err ? err.toString() : file);
			});
		} else {
			// Find out list of proxy generators
			fs.readdir(__dirname + '/../proxies/', (err, files) => {
				const proxies = files.filter(f => f.endsWith('.ejs')).map(f => f.substr(0, f.length - 4));

				// Render the metadata template
				ejs.renderFile(
					__dirname + '/../templates/metadata.ejs',
					{
						metadata: service,
						ejs,
						ujs,
						proxies,
						root: this.rootPath
					}, {_with: false}, (err, html) => {
						if (err) {
							res.status(500).send(err.toString());
						} else {
							res.send(html);
						}
					});
			});
		}
	}

	_routeHandler(req, res, next) {
		if (req.method.toUpperCase() == 'OPTIONS') {
			res.set('Access-Control-Allow-Origin', '*');
			res.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
			res.set('Access-Control-Max-Age', 1000);
			res.set('Access-Control-Allow-Headers', 'origin, x-csrftoken, content-type, accept');
			res.end();
			return;
		}

		const requestUrl = url.parse(req.path).pathname || '';
		if (requestUrl === '/') {
			ejs.renderFile(__dirname + '/../templates/services.ejs', {
				services: Array.from(this.services.entries()).map(entry => ({
					path: `${req.baseUrl}${entry[0]}`,
					metadata: entry[1]
				}))
			}, {_with: false}, function (err, html) {
				if (err) {
					res.status(500).send(err.toString());
				} else {
					res.set('Content-Type', 'text/html');
					res.set('Content-Length', html.length);
					res.set('Cache-Control', 'must-revalidate');
					res.send(html);
				}
			});
		} else if (requestUrl == '/client' || requestUrl == '/client.js') {
			fs.readFile(__dirname + '/../lib/client.js', {encoding: 'utf-8'}, function (err, text) {
				if (err) res.status(404).send('Error reading client library\n\n' + err);
				else {
					res.set('Content-Type', 'application/javascript');
					res.send(uglify(text, 'full'));
				}
			});
		} else if (requestUrl.indexOf('/static/') != -1) {
			const fileName = requestUrl.substring(requestUrl.lastIndexOf('/') + 1);

			if (fileName.indexOf('.js') != -1) {
				res.set('Content-Type', 'application/javascript');
			} else if (fileName.indexOf('.css') != -1) {
				res.set('Content-Type', 'text/css');
			}
			res.sendFile(path.resolve(__dirname + '/../static/' + fileName));
		} else {
			const serviceInstance = this.getService(requestUrl);
			if (serviceInstance && req.method.toUpperCase() == 'GET') {
				this._renderMetadataPage(serviceInstance, req, res);
			} else {
				next();
			}
		}
	}

	getService(pathPrefix) {
		return this.services.get(pathPrefix);
	}
}

const registries = new Map(); // rootPath: string -> registry: ServiceRegistry

/**
 * API Registry middleware for Express/Connect
 * Responds to OPTIONS request, renders a page listing all registered services
 * and serves the NodeJS/browser client library.
 * @param {String} rootPath Mount point of the service registry.
 */
module.exports = function(options) {
	let registry = registries.get(options.rootPath);
	if (!registry) {
		registry = new ServiceRegistry(options);
		registries.set(options.rootPath, registry);
	}
	return registry;
};
<|endoftext|>


<commit_before>(function(root) {
  root.AV = root.AV || {};
  var AV = root.AV;
  var _ = AV._;

  /**
   * Creates a new Relation for the given parent object and key. This
   * constructor should rarely be used directly, but rather created by
   * AV.Object.relation.
   * @param {AV.Object} parent The parent of this relation.
   * @param {String} key The key for this relation on the parent.
   * @see AV.Object#relation
   * @class
   *
   *

   * A class that is used to access all of the children of a many-to-many
   * relationship.  Each instance of AV.Relation is associated with a
   * particular parent object and key.
   *

   */
  AV.Relation = function(parent, key) {
    this.parent = parent;
    this.key = key;
    this.targetClassName = null;
  };

  /**
   * Creates a query that can be used to query the parent objects in this relation.
   * @param {String} parentClass The parent class or name.
   * @param {String} relationKey The relation field key in parent.
   * @param {AV.Object} child The child object.
   * @return {AV.Query}
   */
  AV.Relation.reverseQuery = function(parentClass, relationKey, child){
    var query = new AV.Query(parentClass);
    query.equalTo(relationKey, child._toPointer());
    return query;
  };

  AV.Relation.prototype = {
    /**
     * Makes sure that this relation has the right parent and key.
     */
    _ensureParentAndKey: function(parent, key) {
      this.parent = this.parent || parent;
      this.key = this.key || key;
      if (this.parent !== parent) {
        throw "Internal Error. Relation retrieved from two different Objects.";
      }
      if (this.key !== key) {
        throw "Internal Error. Relation retrieved from two different keys.";
      }
    },

    /**
     * Adds a AV.Object or an array of AV.Objects to the relation.
     * @param {} objects The item or items to add.
     */
    add: function(objects) {
      if (!_.isArray(objects)) {
        objects = [objects];
      }

      var change = new AV.Op.Relation(objects, []);
      this.parent.set(this.key, change);
      this.targetClassName = change._targetClassName;
    },

    /**
     * Removes a AV.Object or an array of AV.Objects from this relation.
     * @param {} objects The item or items to remove.
     */
    remove: function(objects) {
      if (!_.isArray(objects)) {
        objects = [objects];
      }

      var change = new AV.Op.Relation([], objects);
      this.parent.set(this.key, change);
      this.targetClassName = change._targetClassName;
    },

    /**
     * Returns a JSON version of the object suitable for saving to disk.
     * @return {Object}
     */
    toJSON: function() {
      return { "__type": "Relation", "className": this.targetClassName };
    },

    /**
     * Returns a AV.Query that is limited to objects in this
     * relation.
     * @return {AV.Query}
     */
    query: function() {
      var targetClass;
      var query;
      if (!this.targetClassName) {
        targetClass = AV.Object._getSubclass(this.parent.className);
        query = new AV.Query(targetClass);
        query._extraOptions.redirectClassNameForKey = this.key;
      } else {
        targetClass = AV.Object._getSubclass(this.targetClassName);
        query = new AV.Query(targetClass);
      }
      query._addCondition("$relatedTo", "object", this.parent._toPointer());
      query._addCondition("$relatedTo", "key", this.key);

      return query;
    }
  };
}(this));
<commit_msg>add relation constructor type check<commit_after>(function(root) {
  root.AV = root.AV || {};
  var AV = root.AV;
  var _ = AV._;

  /**
   * Creates a new Relation for the given parent object and key. This
   * constructor should rarely be used directly, but rather created by
   * AV.Object.relation.
   * @param {AV.Object} parent The parent of this relation.
   * @param {String} key The key for this relation on the parent.
   * @see AV.Object#relation
   * @class
   *
   *

   * A class that is used to access all of the children of a many-to-many
   * relationship.  Each instance of AV.Relation is associated with a
   * particular parent object and key.
   *

   */
  AV.Relation = function(parent, key) {
    if (! _.isString(key)) {
      throw TypeError('key must be a string');
    }
    this.parent = parent;
    this.key = key;
    this.targetClassName = null;
  };

  /**
   * Creates a query that can be used to query the parent objects in this relation.
   * @param {String} parentClass The parent class or name.
   * @param {String} relationKey The relation field key in parent.
   * @param {AV.Object} child The child object.
   * @return {AV.Query}
   */
  AV.Relation.reverseQuery = function(parentClass, relationKey, child){
    var query = new AV.Query(parentClass);
    query.equalTo(relationKey, child._toPointer());
    return query;
  };

  AV.Relation.prototype = {
    /**
     * Makes sure that this relation has the right parent and key.
     */
    _ensureParentAndKey: function(parent, key) {
      this.parent = this.parent || parent;
      this.key = this.key || key;
      if (this.parent !== parent) {
        throw "Internal Error. Relation retrieved from two different Objects.";
      }
      if (this.key !== key) {
        throw "Internal Error. Relation retrieved from two different keys.";
      }
    },

    /**
     * Adds a AV.Object or an array of AV.Objects to the relation.
     * @param {} objects The item or items to add.
     */
    add: function(objects) {
      if (!_.isArray(objects)) {
        objects = [objects];
      }

      var change = new AV.Op.Relation(objects, []);
      this.parent.set(this.key, change);
      this.targetClassName = change._targetClassName;
    },

    /**
     * Removes a AV.Object or an array of AV.Objects from this relation.
     * @param {} objects The item or items to remove.
     */
    remove: function(objects) {
      if (!_.isArray(objects)) {
        objects = [objects];
      }

      var change = new AV.Op.Relation([], objects);
      this.parent.set(this.key, change);
      this.targetClassName = change._targetClassName;
    },

    /**
     * Returns a JSON version of the object suitable for saving to disk.
     * @return {Object}
     */
    toJSON: function() {
      return { "__type": "Relation", "className": this.targetClassName };
    },

    /**
     * Returns a AV.Query that is limited to objects in this
     * relation.
     * @return {AV.Query}
     */
    query: function() {
      var targetClass;
      var query;
      if (!this.targetClassName) {
        targetClass = AV.Object._getSubclass(this.parent.className);
        query = new AV.Query(targetClass);
        query._extraOptions.redirectClassNameForKey = this.key;
      } else {
        targetClass = AV.Object._getSubclass(this.targetClassName);
        query = new AV.Query(targetClass);
      }
      query._addCondition("$relatedTo", "object", this.parent._toPointer());
      query._addCondition("$relatedTo", "key", this.key);

      return query;
    }
  };
}(this));
<|endoftext|>


<commit_before>var chalk = require('chalk')
var stripUrl = require('url-trim')
var now = require('right-now')
var levels = require('./levels')
var padLeft = require('pad-left')
var padRight = require('pad-right')
var prettyBytes = require('pretty-bytes')
var prettyMs = require('pretty-ms')

var paddings = {
  level: 6,
  method: 6,
  contentLength: 8,
  statusCode: 3,
  elapsed: 7
}

var leftAligns = ['level', 'method']

var ansiStyles = Object.keys(chalk.styles)
var pool = [ 'magenta', 'cyan', 'blue', 'green', 'yellow' ]
var keys = [
  'time',
  'level',
  'message',
  'method',
  'statusCode',
  'elapsed',
  'contentLength',
  'url',
  'type',
  'name'
]

var startTime = now()

module.exports.isStyleObject = function (data) {
  // skip false/undefined/etc
  if (typeof data !== 'object' || !data) {
    return false
  }
  // ensure we have something worth styling
  return keys.some(function (key) {
    return data.hasOwnProperty(key)
  })
}

module.exports.create = function (defaultName) {
  var poolCount = 0
  var poolIndex = {}

  return function render (data) {
    var level = data.level
    var name = data.name

    if (name && name !== defaultName && !poolIndex[name]) {
      poolIndex[name] = pool[poolCount % pool.length]
      poolCount++
    }

    // some default colors
    var defaultColors = {
      level: levels.color(level) || 'yellow',
      name: name ? poolIndex[name] : null,
      time: 'dim',
      statusCode: data.statusCode >= 400 ? 'red' : 'green',
      contentLength: 'dim',
      elapsed: 'dim',
      url: 'bold',
      method: 'dim',
      type: 'dim'
    }

    // possible user overrides
    var colors = data.colors || {}

    if (typeof data.message === 'object') {
      data.message = destructureMessage(data.message)
    }

    // if HTTP, don't log level or 'static'
    if (data.method || data.statusCode || data.contentLength) {
      if (data.level === 'info' || data.level === 'debug') {
        data.level = ''
        level = ''
      }
    }

    // clean up the messages a little
    if (level) {
      data.level = levels.stringify(level)
    }

    // ignore 'static' field since it's assumed
    if (data.type === 'static') data.type = ''

    if (name) {
      data.name = name === defaultName ? '' : ('(' + name + ')')
    }

    if (data.url) data.url = stripUrl(data.url)
    if (data.type) data.type = '(' + data.type + ')'

    var line = []
    var timeOff = String(Math.round((now() - startTime) / 1000) % 10000)
    data.time = '[' + padLeft(timeOff, 4, '0') + ']'

    if (!data.method && !data.statusCode) {
      var emptySpace = paddings.method + paddings.statusCode + 1
      if (!data.message) {
        data.message = padLeft('', emptySpace, ' ')
      }
    }

    // render each of our valid keys
    keys.forEach(function (key) {
      var value = data[key]

      // skip empty data
      if (!value) return

      // compact formatting
      if (key === 'elapsed') value = fixElapsed(value)
      if (key === 'contentLength') value = fixSize(value)

      // pad to length
      if (key in paddings) {
        var padFn = leftAligns.indexOf(key) >= 0 ? padRight : padLeft
        value = padFn.call(padFn, value, paddings[key], ' ')
      }

      // colorize chunk
      var newColor = getColor(key, colors, defaultColors)

      if (newColor) {
        value = chalk[newColor](value)
      }

      line.push(value)
    })
    return line.join(' ')
  }
}

function fixElapsed (time) {
  if (typeof time === 'string' && /s$/i.test(time)) {
    return time
  }
  if (/infinity/i.test(time)) return time
  var ms = parseInt(time, 10)
  return ms > 9999 ? prettyMs(ms) : (ms + 'ms')
}

function fixSize (size) {
  if (typeof size === 'string' && /s$/i.test(size)) {
    return size
  }
  if (/infinity/i.test(size)) return size
  var bytes = parseInt(size, 10)
  return bytes > 9999
    ? prettyBytes(bytes)
      .replace(/ /, '')
    : (bytes + 'B')
}

function getColor (key, colors, defaultColors) {
  // try to apply user style
  var newColor = colors[key]

  // use default if style is invalid
  if (ansiStyles.indexOf(newColor) === -1) {
    newColor = null
  }
  return newColor || defaultColors[key]
}

// destructure a message onto an object if the message
// is an object.
// obj -> str
function destructureMessage (msg) {
  const keys = Object.keys(msg)
  var i = 0
  var j = keys.length
  var res = ''
  for (; i < j; i++) {
    var key = keys[i]
    var val = msg[key]
    res += key + '=' + val
    if (i !== j - 1) res += ', '
  }
  return res
}
<commit_msg>with new design, app names will all use magenta<commit_after>var chalk = require('chalk')
var stripUrl = require('url-trim')
var now = require('right-now')
var levels = require('./levels')
var padLeft = require('pad-left')
var padRight = require('pad-right')
var prettyBytes = require('pretty-bytes')
var prettyMs = require('pretty-ms')

var paddings = {
  method: 6,
  statusCode: 3,
  contentLength: 8,
  elapsed: 7
}

var leftAligns = ['method']

var ansiStyles = Object.keys(chalk.styles)
var keys = [
  'time',
  'level',
  'elapsed',
  'contentLength',
  'message',
  'method',
  'statusCode',
  'url',
  'type',
  'name'
]

var startTime = now()

module.exports.isStyleObject = function (data) {
  // skip false/undefined/etc
  if (typeof data !== 'object' || !data) {
    return false
  }
  // ensure we have something worth styling
  return keys.some(function (key) {
    return data.hasOwnProperty(key)
  })
}

module.exports.create = function (defaultName) {
  return function render (data) {
    var level = data.level
    var name = data.name

    // some default colors
    var defaultColors = {
      level: levels.color(level) || 'yellow',
      name: 'magenta',
      time: 'dim',
      statusCode: data.statusCode >= 400 ? 'red' : 'green',
      contentLength: 'dim',
      elapsed: 'dim',
      url: 'bold',
      method: 'dim',
      type: 'dim'
    }

    // possible user overrides
    var colors = data.colors || {}

    if (typeof data.message === 'object') {
      data.message = destructureMessage(data.message)
    }

    // if HTTP, don't log level or 'static'
    // if (data.method || data.statusCode || data.contentLength) {
    //   if (data.level === 'info' || data.level === 'debug') {
    //     data.level = ''
    //     level = ''
    //   }
    // }

    // clean up the messages a little
    if (level) {
      data.level = levels.stringify(level)
    }

    // ignore 'static' field since it's assumed
    if (data.type === 'static') data.type = ''

    if (name) {
      data.name = name === defaultName ? '' : ('(' + name + ')')
    }

    if (data.url) data.url = stripUrl(data.url)
    if (data.type) data.type = '(' + data.type + ')'

    var line = []
    var timeOff = String(Math.round((now() - startTime) / 1000) % 10000)
    data.time = '[' + padLeft(timeOff, 4, '0') + ']'

    if (!data.message) {
      data.level = level = ''
    }

    var alignLeft = true

    // render each of our valid keys
    keys.forEach(function (key) {
      var value = data[key]

      // skip empty data
      if (!value && typeof value !== 'number') {
        return
      }

      // compact formatting
      if (key === 'elapsed') value = fixElapsed(value)
      if (key === 'contentLength') value = fixSize(value)

      // pad to length
      if (key in paddings) {
        var left = alignLeft || leftAligns.indexOf(key) >= 0
        var padFn = left ? padRight : padLeft
        value = padFn.call(padFn, value, paddings[key], ' ')
        alignLeft = false
      }

      // colorize chunk
      var newColor = getColor(key, colors, defaultColors)

      if (newColor) {
        value = chalk[newColor](value)
      }

      line.push(value)
    })
    return line.join(' ')
  }
}

function fixElapsed (time) {
  if (typeof time === 'string' && /s$/i.test(time)) {
    return time
  }
  if (/infinity/i.test(time)) return time
  var ms = parseInt(time, 10)
  return ms > 9999 ? prettyMs(ms) : (ms + 'ms')
}

function fixSize (size) {
  if (typeof size === 'string' && /s$/i.test(size)) {
    return size
  }
  if (/infinity/i.test(size)) return size
  var bytes = parseInt(size, 10)
  return bytes > 9999
    ? prettyBytes(bytes)
      .replace(/ /, '')
    : (bytes + 'B')
}

function getColor (key, colors, defaultColors) {
  // try to apply user style
  var newColor = colors[key]

  // use default if style is invalid
  if (ansiStyles.indexOf(newColor) === -1) {
    newColor = null
  }
  return newColor || defaultColors[key]
}

// destructure a message onto an object if the message
// is an object.
// obj -> str
function destructureMessage (msg) {
  const keys = Object.keys(msg)
  var i = 0
  var j = keys.length
  var res = ''
  for (; i < j; i++) {
    var key = keys[i]
    var val = msg[key]
    res += key + '=' + val
    if (i !== j - 1) res += ', '
  }
  return res
}
<|endoftext|>


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

(function () {
    "use strict";

    var fs = require("fs"),
        util = require("util");

    var tmp = require("tmp"),
        Q = require("q");

    /**
     * Asynchronously get a unique temporary path name.
     *
     * @private
     * @return {Promise.<string>}
     */
    function _getTempPath() {
        return Q.ninvoke(tmp, "tmpName");
    }

    /**
     * Abstract renderer class for a given document. Converts components to assets on disk.
     *
     * @constructor
     * @param {Generator} generator
     * @param {object} config
     * @param {Logger} logger
     * @param {Document} document
     */
    function BaseRenderer(generator, config, logger, document) {
        this._generator = generator;
        this._document = document;
        this._logger = logger;

        if (config.hasOwnProperty("use-smart-scaling")) {
            this._useSmartScaling = !!config["use-smart-scaling"];
        }

        if (config.hasOwnProperty("include-ancestor-masks")) {
            this._includeAncestorMasks = !!config["include-ancestor-masks"];
        }
    }

    /**
     * @type {boolean=}
     */
    BaseRenderer.prototype._useSmartScaling = undefined;

    /**
     * @type {boolean=}
     */
    BaseRenderer.prototype._includeAncestorMasks = undefined;

    /**
     * Convert a value in a given unit, at particular resolution, to a value in pixels per inch.
     *
     * @param {number} value
     * @param {string} unit
     * @param {number} ppi
     * @return {number}
     */
    BaseRenderer.prototype._convertToPixels = function (value, unit, ppi) {
        if (!value || !unit || unit === "px") {
            return value;
        }

        if (unit === "in") {
            return value * ppi;
        } else if (unit === "mm") {
            return (value / 25.4) * ppi;
        } else if (unit === "cm") {
            return (value / 2.54) * ppi;
        } else {
            this._logger.error("An invalid length unit was specified: " + unit);
        }
    };

    /**
     * Render a given component to an asset
     *
     * @param {Layer} layer
     * @param {Component} component
     * @return {Promise.<string>} Resolves with the temporary path of the new asset once rendering is complete.
     */
    BaseRenderer.prototype.render = function () { /* layer, component, ...*/
        var dataPromise = this._getData.apply(this, arguments),
            pathPromise = _getTempPath();

        return Q.all([dataPromise, pathPromise])
            .spread(this._writeData.bind(this))
            .thenResolve(pathPromise);
    };

    /**
     * SVG asset renderer.
     *
     * @constructor
     * @extends BaseRenderer
     */
    function SVGRenderer(generator, config, logger, document) {
        BaseRenderer.call(this, generator, config, logger, document);
    }

    util.inherits(SVGRenderer, BaseRenderer);

    /**
     * Get SVG data for the given component.
     *
     * @private
     * @param {Layer] layer
     * @param {Component} component
     * @return {Promise.<string>} Resolves with the SVG data.
     */
    SVGRenderer.prototype._getData = function (layer, component) {
        var scale = component.scale || 1,
            settings = {
                scale: scale
            };

        return this._generator.getSVG(layer.document.id, layer.id, settings);
    };

    /**
     * Write the render data to disk at the given path.
     *
     * @private
     * @param {string} data
     * @param {string} path Temporary path at which to write data
     * @return {Promise} Resolves when data has been written
     */
    SVGRenderer.prototype._writeData = function (data, path) {
        return Q.nfcall(fs.writeFile, path, data, { encoding: "utf8" });
    };

    /**
     * Return a new SVGRenderer object.
     */
    function createSVGRenderer(generator, config, logger, document) {
        return new SVGRenderer(generator, config, logger, document);
    }

    /**
     * Pixmap asset renderer.
     *
     * @constructor
     * @extends BaseRenderer
     */
    function PixmapRenderer(generator, config, logger, document) {
        BaseRenderer.call(this, generator, config, logger, document);
    }

    util.inherits(PixmapRenderer, BaseRenderer);

    /**
     * Asynchronously get exact bounds for the given component. These bounds
     * should be used if layer pixmap is sized explicitly or is scaled by a
     * non-integral multiple or if there is a mask or there are enabled layer
     * effects.
     *
     * @private
     * @param {Layer} layer
     * @param {Component} component
     * @return {Promise.<{inputRect: Bounds, outputRect: Bounds, expectedWidth: number,
     *      expectedHeight: number, getPadding: function (number, number): number }>}
     */
    PixmapRenderer.prototype._getSettingsWithExactBounds = function (layer, component) {
        var ppi = this._document.resolution;

        return layer.getExactBounds().then(function (exactBounds) {
            if (exactBounds.right <= exactBounds.left || exactBounds.bottom <= exactBounds.top) {
                throw new Error("Refusing to render pixmap with zero bounds.");
            }

            var scaleSettings = {
                    width:  this._convertToPixels(component.width,  component.widthUnit, ppi),
                    height: this._convertToPixels(component.height, component.heightUnit, ppi),
                    scaleX: component.scaleX || component.scale,
                    scaleY: component.scaleY || component.scale,
                    // Backwards compatibility
                    scale:  component.scale
                },
                // Mask
                maskBounds = layer.mask && layer.mask.bounds,
                // Static: User provided
                staticBounds  = this._generator.getDeepBounds(layer),
                // Visible: User provided + effects
                visibleBounds = exactBounds,
                // Padded: User provided + effects + padding through layer mask
                paddedBounds  = !maskBounds ? exactBounds : {
                    left:   Math.min(exactBounds.left,   maskBounds.left),
                    top:    Math.min(exactBounds.top,    maskBounds.top),
                    right:  Math.max(exactBounds.right,  maskBounds.right),
                    bottom: Math.max(exactBounds.bottom, maskBounds.bottom)
                },
                pixmapSettings = this._generator.getPixmapParams(scaleSettings, staticBounds,
                    visibleBounds, paddedBounds);

            return pixmapSettings;
        }.bind(this));
    };

    /**
     * Synchronously get approximate bounds for the given component. These bounds
     * should only be used if layer pixmap is scaled by an integral multiple and if
     * there is no mask and there are no enabled layer effects.
     *
     * @private
     * @param {Layer} layer
     * @param {Component} component
     * @return {{inputRect: Bounds, outputRect: Bounds, expectedWidth: number,
     *      expectedHeight: number, getPadding: function (number, number): number }}
     */
    PixmapRenderer.prototype._getSettingsWithApproximateBounds = function (layer, component) {
        var scalar = component.scale;
        if (!scalar) {
            scalar = 1;
        }

        var inputRect = layer.bounds,
            outputRect = inputRect.scale(scalar);

        if (outputRect.right <= outputRect.left || outputRect.bottom <= outputRect.top) {
            throw new Error("Refusing to render pixmap with zero bounds.");
        }

        return {
            inputRect: inputRect,
            outputRect: outputRect,
            expectedWidth: outputRect.right - outputRect.left,
            expectedHeight: outputRect.bottom - outputRect.top,
            getPadding: function () {
                return {
                    top: 0,
                    right: 0,
                    bottom: 0,
                    left: 0
                };
            }
        };
    };

    /**
     * Get pixmap data for the given component.
     *
     * @private
     * @param {Layer] layer
     * @param {Component} component
     * @return {Promise.<{pixmap: Pixmap, settings: object}>} Resolves with the pixmap data and settings
     */
    PixmapRenderer.prototype._getData = function (layer, component) {
        // The exact bounds computation, which is expensive, is skipped if all of the
        // following conditions hold:
        // 1. The component is either not scaled, or is only scaled by an integral
        //    factor (i.e, 100%, 200%, etc.)
        // 2. The layer does not have an enabled mask
        // 3. The layer does not have any enabled layer effects
        var hasComplexTransform = (component.hasOwnProperty("scale") && component.scale % 1 !== 0) ||
                component.hasOwnProperty("width") || component.hasOwnProperty("height"),
            hasMask = layer.mask && layer.mask.enabled,
            hasEffects = layer.layerEffects && layer.layerEffects.isEnabled(),
            settingsPromise;

        if (hasComplexTransform || hasMask || hasEffects) {
            settingsPromise = this._getSettingsWithExactBounds(layer, component);
        } else {
            try {
                settingsPromise = new Q(this._getSettingsWithApproximateBounds(layer, component));
            } catch (ex) {
                settingsPromise = Q.reject(ex);
            }
        }

        return settingsPromise.then(function (pixmapSettings) {
            if (this._useSmartScaling !== undefined) {
                pixmapSettings.useSmartScaling = this._useSmartScaling;
            }

            if (this._useSmartScaling !== undefined) {
                pixmapSettings.includeAncestorMasks = this._includeAncestorMasks;
            }

            return this._generator.getPixmap(this._document.id, layer.id, pixmapSettings).then(function (pixmap) {
                var padding = pixmapSettings.hasOwnProperty("getPadding") ?
                        pixmapSettings.getPadding(pixmap.width, pixmap.height) : undefined,
                    quality = component.quality,
                    format = component.extension,
                    ppi = this._document.resolution,
                    settings = {
                        quality: quality,
                        format: format,
                        ppi: ppi,
                        padding: padding
                    };

                return {
                    pixmap: pixmap,
                    settings: settings
                };
            }.bind(this));
        }.bind(this));
    };

    /**
     * Write the render data to disk at the given path.
     *
     * @private
     * @param {{pixmap: Pixmap, settings: object}} data
     * @param {string} path Temporary path at which to write data
     * @return {Promise} Resolves when data has been written
     */
    PixmapRenderer.prototype._writeData = function (data, path) {
        return this._generator.savePixmap(data.pixmap, path, data.settings);
    };

    /**
     * Return a new PixmapRenderer object.
     */
    function createPixmapRenderer(generator, config, logger, document) {
        return new PixmapRenderer(generator, config, logger, document);
    }

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

(function () {
    "use strict";

    var fs = require("fs"),
        util = require("util");

    var tmp = require("tmp"),
        Q = require("q");

    /**
     * Asynchronously get a unique temporary path name.
     *
     * @private
     * @return {Promise.<string>}
     */
    function _getTempPath() {
        return Q.ninvoke(tmp, "tmpName");
    }

    /**
     * Abstract renderer class for a given document. Converts components to assets on disk.
     *
     * @constructor
     * @param {Generator} generator
     * @param {object} config
     * @param {Logger} logger
     * @param {Document} document
     */
    function BaseRenderer(generator, config, logger, document) {
        this._generator = generator;
        this._document = document;
        this._logger = logger;

        if (config.hasOwnProperty("use-smart-scaling")) {
            this._useSmartScaling = !!config["use-smart-scaling"];
        }

        if (config.hasOwnProperty("include-ancestor-masks")) {
            this._includeAncestorMasks = !!config["include-ancestor-masks"];
        }

        if (config.hasOwnProperty("allow-dither")) {
            this._allowDither = !!config["allow-dither"];
        }
    }

    /**
     * @type {boolean=}
     */
    BaseRenderer.prototype._useSmartScaling = undefined;

    /**
     * @type {boolean=}
     */
    BaseRenderer.prototype._includeAncestorMasks = undefined;

    /**
     * @type {boolean=}
     */
    BaseRenderer.prototype._allowDither = undefined;

    /**
     * Convert a value in a given unit, at particular resolution, to a value in pixels per inch.
     *
     * @param {number} value
     * @param {string} unit
     * @param {number} ppi
     * @return {number}
     */
    BaseRenderer.prototype._convertToPixels = function (value, unit, ppi) {
        if (!value || !unit || unit === "px") {
            return value;
        }

        if (unit === "in") {
            return value * ppi;
        } else if (unit === "mm") {
            return (value / 25.4) * ppi;
        } else if (unit === "cm") {
            return (value / 2.54) * ppi;
        } else {
            this._logger.error("An invalid length unit was specified: " + unit);
        }
    };

    /**
     * Render a given component to an asset
     *
     * @param {Layer} layer
     * @param {Component} component
     * @return {Promise.<string>} Resolves with the temporary path of the new asset once rendering is complete.
     */
    BaseRenderer.prototype.render = function () { /* layer, component, ...*/
        var dataPromise = this._getData.apply(this, arguments),
            pathPromise = _getTempPath();

        return Q.all([dataPromise, pathPromise])
            .spread(this._writeData.bind(this))
            .thenResolve(pathPromise);
    };

    /**
     * SVG asset renderer.
     *
     * @constructor
     * @extends BaseRenderer
     */
    function SVGRenderer(generator, config, logger, document) {
        BaseRenderer.call(this, generator, config, logger, document);
    }

    util.inherits(SVGRenderer, BaseRenderer);

    /**
     * Get SVG data for the given component.
     *
     * @private
     * @param {Layer] layer
     * @param {Component} component
     * @return {Promise.<string>} Resolves with the SVG data.
     */
    SVGRenderer.prototype._getData = function (layer, component) {
        var scale = component.scale || 1,
            settings = {
                scale: scale
            };

        return this._generator.getSVG(layer.document.id, layer.id, settings);
    };

    /**
     * Write the render data to disk at the given path.
     *
     * @private
     * @param {string} data
     * @param {string} path Temporary path at which to write data
     * @return {Promise} Resolves when data has been written
     */
    SVGRenderer.prototype._writeData = function (data, path) {
        return Q.nfcall(fs.writeFile, path, data, { encoding: "utf8" });
    };

    /**
     * Return a new SVGRenderer object.
     */
    function createSVGRenderer(generator, config, logger, document) {
        return new SVGRenderer(generator, config, logger, document);
    }

    /**
     * Pixmap asset renderer.
     *
     * @constructor
     * @extends BaseRenderer
     */
    function PixmapRenderer(generator, config, logger, document) {
        BaseRenderer.call(this, generator, config, logger, document);
    }

    util.inherits(PixmapRenderer, BaseRenderer);

    /**
     * Asynchronously get exact bounds for the given component. These bounds
     * should be used if layer pixmap is sized explicitly or is scaled by a
     * non-integral multiple or if there is a mask or there are enabled layer
     * effects.
     *
     * @private
     * @param {Layer} layer
     * @param {Component} component
     * @return {Promise.<{inputRect: Bounds, outputRect: Bounds, expectedWidth: number,
     *      expectedHeight: number, getPadding: function (number, number): number }>}
     */
    PixmapRenderer.prototype._getSettingsWithExactBounds = function (layer, component) {
        var ppi = this._document.resolution;

        return layer.getExactBounds().then(function (exactBounds) {
            if (exactBounds.right <= exactBounds.left || exactBounds.bottom <= exactBounds.top) {
                throw new Error("Refusing to render pixmap with zero bounds.");
            }

            var scaleSettings = {
                    width:  this._convertToPixels(component.width,  component.widthUnit, ppi),
                    height: this._convertToPixels(component.height, component.heightUnit, ppi),
                    scaleX: component.scaleX || component.scale,
                    scaleY: component.scaleY || component.scale,
                    // Backwards compatibility
                    scale:  component.scale
                },
                // Mask
                maskBounds = layer.mask && layer.mask.bounds,
                // Static: User provided
                staticBounds  = this._generator.getDeepBounds(layer),
                // Visible: User provided + effects
                visibleBounds = exactBounds,
                // Padded: User provided + effects + padding through layer mask
                paddedBounds  = !maskBounds ? exactBounds : {
                    left:   Math.min(exactBounds.left,   maskBounds.left),
                    top:    Math.min(exactBounds.top,    maskBounds.top),
                    right:  Math.max(exactBounds.right,  maskBounds.right),
                    bottom: Math.max(exactBounds.bottom, maskBounds.bottom)
                },
                pixmapSettings = this._generator.getPixmapParams(scaleSettings, staticBounds,
                    visibleBounds, paddedBounds);

            return pixmapSettings;
        }.bind(this));
    };

    /**
     * Synchronously get approximate bounds for the given component. These bounds
     * should only be used if layer pixmap is scaled by an integral multiple and if
     * there is no mask and there are no enabled layer effects.
     *
     * @private
     * @param {Layer} layer
     * @param {Component} component
     * @return {{inputRect: Bounds, outputRect: Bounds, expectedWidth: number,
     *      expectedHeight: number, getPadding: function (number, number): number }}
     */
    PixmapRenderer.prototype._getSettingsWithApproximateBounds = function (layer, component) {
        var scalar = component.scale;
        if (!scalar) {
            scalar = 1;
        }

        var inputRect = layer.bounds,
            outputRect = inputRect.scale(scalar);

        if (outputRect.right <= outputRect.left || outputRect.bottom <= outputRect.top) {
            throw new Error("Refusing to render pixmap with zero bounds.");
        }

        return {
            inputRect: inputRect,
            outputRect: outputRect,
            expectedWidth: outputRect.right - outputRect.left,
            expectedHeight: outputRect.bottom - outputRect.top,
            getPadding: function () {
                return {
                    top: 0,
                    right: 0,
                    bottom: 0,
                    left: 0
                };
            }
        };
    };

    /**
     * Get pixmap data for the given component.
     *
     * @private
     * @param {Layer] layer
     * @param {Component} component
     * @return {Promise.<{pixmap: Pixmap, settings: object}>} Resolves with the pixmap data and settings
     */
    PixmapRenderer.prototype._getData = function (layer, component) {
        // The exact bounds computation, which is expensive, is skipped if all of the
        // following conditions hold:
        // 1. The component is either not scaled, or is only scaled by an integral
        //    factor (i.e, 100%, 200%, etc.)
        // 2. The layer does not have an enabled mask
        // 3. The layer does not have any enabled layer effects
        var hasComplexTransform = (component.hasOwnProperty("scale") && component.scale % 1 !== 0) ||
                component.hasOwnProperty("width") || component.hasOwnProperty("height"),
            hasMask = layer.mask && layer.mask.enabled,
            hasEffects = layer.layerEffects && layer.layerEffects.isEnabled(),
            settingsPromise;

        if (hasComplexTransform || hasMask || hasEffects) {
            settingsPromise = this._getSettingsWithExactBounds(layer, component);
        } else {
            try {
                settingsPromise = new Q(this._getSettingsWithApproximateBounds(layer, component));
            } catch (ex) {
                settingsPromise = Q.reject(ex);
            }
        }

        return settingsPromise.then(function (pixmapSettings) {
            if (this._useSmartScaling !== undefined) {
                pixmapSettings.useSmartScaling = this._useSmartScaling;
            }

            if (this._useSmartScaling !== undefined) {
                pixmapSettings.includeAncestorMasks = this._includeAncestorMasks;
            }

            if (this._allowDither !== undefined) {
                pixmapSettings.allowDither = this._allowDither;
                if (this._allowDither) {
                    // force dithering, even if it is off in the user's color settings,
                    // since they explicitly enabled it in Generator
                    pixmapSettings.useColorSettingsDither = false;
                }
            }

            return this._generator.getPixmap(this._document.id, layer.id, pixmapSettings).then(function (pixmap) {
                var padding = pixmapSettings.hasOwnProperty("getPadding") ?
                        pixmapSettings.getPadding(pixmap.width, pixmap.height) : undefined,
                    quality = component.quality,
                    format = component.extension,
                    ppi = this._document.resolution,
                    settings = {
                        quality: quality,
                        format: format,
                        ppi: ppi,
                        padding: padding
                    };

                return {
                    pixmap: pixmap,
                    settings: settings
                };
            }.bind(this));
        }.bind(this));
    };

    /**
     * Write the render data to disk at the given path.
     *
     * @private
     * @param {{pixmap: Pixmap, settings: object}} data
     * @param {string} path Temporary path at which to write data
     * @return {Promise} Resolves when data has been written
     */
    PixmapRenderer.prototype._writeData = function (data, path) {
        return this._generator.savePixmap(data.pixmap, path, data.settings);
    };

    /**
     * Return a new PixmapRenderer object.
     */
    function createPixmapRenderer(generator, config, logger, document) {
        return new PixmapRenderer(generator, config, logger, document);
    }

    exports.createPixmapRenderer = createPixmapRenderer;
    exports.createSVGRenderer = createSVGRenderer;
}());<|endoftext|>