<commit_before>'use strict'

var path = require('path')
var fs = require('fs-plus')
var CSON = null

var COMPILERS = {
  '.js': require('./babel'),
  '.ts': require('./typescript'),
  '.coffee': require('./coffee-script')
}

var cacheStats = {}
var cacheDirectory = null

exports.setAtomHomeDirectory = function (atomHome) {
  var cacheDir = path.join(atomHome, 'compile-cache')
  if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) {
    cacheDir = path.join(cacheDir, 'root')
  }
  this.setCacheDirectory(cacheDir)
}

exports.setCacheDirectory = function (directory) {
  cacheDirectory = directory
}

exports.getCacheDirectory = function () {
  return cacheDirectory
}

exports.addPathToCache = function (filePath, atomHome) {
  this.setAtomHomeDirectory(atomHome)
  var extension = path.extname(filePath)

  if (extension === '.cson') {
    if (!CSON) {
      CSON = require('season')
      CSON.setCacheDir(this.getCacheDirectory())
    }
    CSON.readFileSync(filePath)
  } else {
    var compiler = COMPILERS[extension]
    if (compiler) {
      compileFileAtPath(compiler, filePath, extension)
    }
  }
}

exports.getCacheStats = function () {
  return cacheStats
}

exports.resetCacheStats = function () {
  Object.keys(COMPILERS).forEach(function (extension) {
    cacheStats[extension] = {
      hits: 0,
      misses: 0
    }
  })
}

function compileFileAtPath (compiler, filePath, extension) {
  var sourceCode = fs.readFileSync(filePath, 'utf8')
  if (compiler.shouldCompile(sourceCode, filePath)) {
    var cachePath = compiler.getCachePath(sourceCode, filePath)
    var compiledCode = readCachedJavascript(cachePath)
    if (compiledCode != null) {
      cacheStats[extension].hits++
    } else {
      cacheStats[extension].misses++
      compiledCode = addSourceURL(compiler.compile(sourceCode, filePath), filePath)
      writeCachedJavascript(cachePath, compiledCode)
    }
    return compiledCode
  }
  return sourceCode
}

function readCachedJavascript (relativeCachePath) {
  var cachePath = path.join(cacheDirectory, relativeCachePath)
  if (fs.isFileSync(cachePath)) {
    try {
      return fs.readFileSync(cachePath, 'utf8')
    } catch (error) {}
  }
  return null
}

function writeCachedJavascript (relativeCachePath, code) {
  var cachePath = path.join(cacheDirectory, relativeCachePath)
  fs.writeFileSync(cachePath, code, 'utf8')
}

function addSourceURL (jsCode, filePath) {
  if (process.platform === 'win32') {
    filePath = '/' + path.resolve(filePath).replace(/\\/g, '/')
  }
  return jsCode + '\n' + '//# sourceURL=' + encodeURI(filePath) + '\n'
}

var INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/mg

require('source-map-support').install({
  handleUncaughtExceptions: false,

  // Most of this logic is the same as the default implementation in the
  // source-map-support module, but we've overridden it to read the javascript
  // code from our cache directory.
  retrieveSourceMap: function (filePath) {
    if (!cacheDirectory || !fs.isFileSync(filePath)) {
      return null
    }

    try {
      var sourceCode = fs.readFileSync(filePath, 'utf8')
    } catch (error) {
      console.warn('Error reading source file', error.stack)
      return null
    }

    var compiler = COMPILERS[path.extname(filePath)]

    try {
      var fileData = readCachedJavascript(compiler.getCachePath(sourceCode, filePath))
    } catch (error) {
      console.warn('Error reading compiled file', error.stack)
      return null
    }

    if (fileData == null) {
      return null
    }

    var match, lastMatch
    INLINE_SOURCE_MAP_REGEXP.lastIndex = 0
    while ((match = INLINE_SOURCE_MAP_REGEXP.exec(fileData))) {
      lastMatch = match
    }
    if (lastMatch == null) {
      return null
    }

    var sourceMappingURL = lastMatch[1]
    var rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1)

    try {
      var sourceMap = JSON.parse(new Buffer(rawData, 'base64'))
    } catch (error) {
      console.warn('Error parsing source map', error.stack)
      return null
    }

    return {
      map: sourceMap,
      url: null
    }
  }
})

Error.stackTraceLimit = 30

var prepareStackTraceWithSourceMapping = Error.prepareStackTrace

let prepareStackTrace = prepareStackTraceWithSourceMapping

function prepareStackTraceWithRawStackAssignment (error, frames) {
  if (error.rawStack) { // avoid infinite recursion
    return prepareStackTraceWithSourceMapping(error, frames)
  } else {
    error.rawStack = frames
    return prepareStackTrace(error, frames)
  }
}

Object.defineProperty(Error, 'prepareStackTrace', {
  get: function () {
    return prepareStackTraceWithRawStackAssignment
  },

  set: function (newValue) {
    prepareStackTrace = newValue
    process.nextTick(function () {
      prepareStackTrace = prepareStackTraceWithSourceMapping
    })
  }
})

Error.prototype.getRawStack = function () { // eslint-disable-line no-extend-native
  // Access this.stack to ensure prepareStackTrace has been run on this error
  // because it assigns this.rawStack as a side-effect
  this.stack
  return this.rawStack
}

Object.keys(COMPILERS).forEach(function (extension) {
  var compiler = COMPILERS[extension]

  Object.defineProperty(require.extensions, extension, {
    enumerable: true,
    writable: false,
    value: function (module, filePath) {
      var code = compileFileAtPath(compiler, filePath, extension)
      return module._compile(code, filePath)
    }
  })
})

exports.resetCacheStats()
<commit_msg>There is no ES6 in compile-cache.js<commit_after>'use strict'

var path = require('path')
var fs = require('fs-plus')
var CSON = null

var COMPILERS = {
  '.js': require('./babel'),
  '.ts': require('./typescript'),
  '.coffee': require('./coffee-script')
}

var cacheStats = {}
var cacheDirectory = null

exports.setAtomHomeDirectory = function (atomHome) {
  var cacheDir = path.join(atomHome, 'compile-cache')
  if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) {
    cacheDir = path.join(cacheDir, 'root')
  }
  this.setCacheDirectory(cacheDir)
}

exports.setCacheDirectory = function (directory) {
  cacheDirectory = directory
}

exports.getCacheDirectory = function () {
  return cacheDirectory
}

exports.addPathToCache = function (filePath, atomHome) {
  this.setAtomHomeDirectory(atomHome)
  var extension = path.extname(filePath)

  if (extension === '.cson') {
    if (!CSON) {
      CSON = require('season')
      CSON.setCacheDir(this.getCacheDirectory())
    }
    CSON.readFileSync(filePath)
  } else {
    var compiler = COMPILERS[extension]
    if (compiler) {
      compileFileAtPath(compiler, filePath, extension)
    }
  }
}

exports.getCacheStats = function () {
  return cacheStats
}

exports.resetCacheStats = function () {
  Object.keys(COMPILERS).forEach(function (extension) {
    cacheStats[extension] = {
      hits: 0,
      misses: 0
    }
  })
}

function compileFileAtPath (compiler, filePath, extension) {
  var sourceCode = fs.readFileSync(filePath, 'utf8')
  if (compiler.shouldCompile(sourceCode, filePath)) {
    var cachePath = compiler.getCachePath(sourceCode, filePath)
    var compiledCode = readCachedJavascript(cachePath)
    if (compiledCode != null) {
      cacheStats[extension].hits++
    } else {
      cacheStats[extension].misses++
      compiledCode = addSourceURL(compiler.compile(sourceCode, filePath), filePath)
      writeCachedJavascript(cachePath, compiledCode)
    }
    return compiledCode
  }
  return sourceCode
}

function readCachedJavascript (relativeCachePath) {
  var cachePath = path.join(cacheDirectory, relativeCachePath)
  if (fs.isFileSync(cachePath)) {
    try {
      return fs.readFileSync(cachePath, 'utf8')
    } catch (error) {}
  }
  return null
}

function writeCachedJavascript (relativeCachePath, code) {
  var cachePath = path.join(cacheDirectory, relativeCachePath)
  fs.writeFileSync(cachePath, code, 'utf8')
}

function addSourceURL (jsCode, filePath) {
  if (process.platform === 'win32') {
    filePath = '/' + path.resolve(filePath).replace(/\\/g, '/')
  }
  return jsCode + '\n' + '//# sourceURL=' + encodeURI(filePath) + '\n'
}

var INLINE_SOURCE_MAP_REGEXP = /\/\/[#@]\s*sourceMappingURL=([^'"\n]+)\s*$/mg

require('source-map-support').install({
  handleUncaughtExceptions: false,

  // Most of this logic is the same as the default implementation in the
  // source-map-support module, but we've overridden it to read the javascript
  // code from our cache directory.
  retrieveSourceMap: function (filePath) {
    if (!cacheDirectory || !fs.isFileSync(filePath)) {
      return null
    }

    try {
      var sourceCode = fs.readFileSync(filePath, 'utf8')
    } catch (error) {
      console.warn('Error reading source file', error.stack)
      return null
    }

    var compiler = COMPILERS[path.extname(filePath)]

    try {
      var fileData = readCachedJavascript(compiler.getCachePath(sourceCode, filePath))
    } catch (error) {
      console.warn('Error reading compiled file', error.stack)
      return null
    }

    if (fileData == null) {
      return null
    }

    var match, lastMatch
    INLINE_SOURCE_MAP_REGEXP.lastIndex = 0
    while ((match = INLINE_SOURCE_MAP_REGEXP.exec(fileData))) {
      lastMatch = match
    }
    if (lastMatch == null) {
      return null
    }

    var sourceMappingURL = lastMatch[1]
    var rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1)

    try {
      var sourceMap = JSON.parse(new Buffer(rawData, 'base64'))
    } catch (error) {
      console.warn('Error parsing source map', error.stack)
      return null
    }

    return {
      map: sourceMap,
      url: null
    }
  }
})

Error.stackTraceLimit = 30

var prepareStackTraceWithSourceMapping = Error.prepareStackTrace
var prepareStackTrace = prepareStackTraceWithSourceMapping

function prepareStackTraceWithRawStackAssignment (error, frames) {
  if (error.rawStack) { // avoid infinite recursion
    return prepareStackTraceWithSourceMapping(error, frames)
  } else {
    error.rawStack = frames
    return prepareStackTrace(error, frames)
  }
}

Object.defineProperty(Error, 'prepareStackTrace', {
  get: function () {
    return prepareStackTraceWithRawStackAssignment
  },

  set: function (newValue) {
    prepareStackTrace = newValue
    process.nextTick(function () {
      prepareStackTrace = prepareStackTraceWithSourceMapping
    })
  }
})

Error.prototype.getRawStack = function () { // eslint-disable-line no-extend-native
  // Access this.stack to ensure prepareStackTrace has been run on this error
  // because it assigns this.rawStack as a side-effect
  this.stack
  return this.rawStack
}

Object.keys(COMPILERS).forEach(function (extension) {
  var compiler = COMPILERS[extension]

  Object.defineProperty(require.extensions, extension, {
    enumerable: true,
    writable: false,
    value: function (module, filePath) {
      var code = compileFileAtPath(compiler, filePath, extension)
      return module._compile(code, filePath)
    }
  })
})

exports.resetCacheStats()
<|endoftext|>


<commit_before>var globals = require('../globals'),
  util = require('../util'),
  vlfield = require('../field'),
  Encoding = require('../Encoding');

module.exports = function(encoding, stats) {
  return {
    opacity: estimateOpacity(encoding, stats),
    // colorRange: colorRange(encoding, stats)
  };
};

function estimateOpacity(encoding,stats) {
  if (!stats) {
    return 1;
  }

  var numPoints = 0;
  var multipleSkewFactor;
  var maxbins = encoding.config('maxbins');

  if (encoding.isAggregate()) { // aggregate plot
    numPoints = 1;

    //  get number of points in each "cell"
    //  by calculating product of cardinality
    //  for each non faceting and non-ordinal X / Y fields
    //  note that ordinal x,y are not include since we can
    //  consider that ordinal x are subdividing the cell into subcells anyway
    encoding.forEach(function(encType, field) {

      if (encType !== ROW && encType !== COL &&
          !((encType === X || encType === Y) &&
          vlfield.isOrdinalScale(field, Encoding.isType))
        ) {
        numPoints *= encoding.cardinality(encType, stats);
      }
    });

    multipleSkewFactor = 0.2; // aggregated plot should have less skew

  } else { // raw plot
    numPoints = stats.count;
    multipleSkewFactor = 0.8;
  }

  // small multiples divide number of points
  var numMultiples = 1;
  if (encoding.has(ROW)) {
    // 0.8  because of skew
    numMultiples *= multipleSkewFactor * encoding.cardinality(ROW, stats);
  }

  if (encoding.has(COL)) {
    numMultiples *= multipleSkewFactor * encoding.cardinality(COL, stats);
  }
  numPoints /= numMultiples;

  // console.log('numPoints', numPoints, numMultiples);

  var opacity = 0;
  if (numPoints < 20) {
    opacity = 1;
  } else if (numPoints < 200) {
    opacity = 0.7;
  } else if (numPoints < 1000) {
    opacity = 0.6;
  } else {
    opacity = 0.3;
  }

  return opacity;
}

<commit_msg>remove multipleSkewFactor, only consider small multiples if data is raw<commit_after>var globals = require('../globals'),
  util = require('../util'),
  vlfield = require('../field'),
  Encoding = require('../Encoding');

module.exports = function(encoding, stats) {
  return {
    opacity: estimateOpacity(encoding, stats),
    // colorRange: colorRange(encoding, stats)
  };
};

function estimateOpacity(encoding,stats) {
  if (!stats) {
    return 1;
  }

  var numPoints = 0;
  var maxbins = encoding.config('maxbins');

  if (encoding.isAggregate()) { // aggregate plot
    numPoints = 1;

    //  get number of points in each "cell"
    //  by calculating product of cardinality
    //  for each non faceting and non-ordinal X / Y fields
    //  note that ordinal x,y are not include since we can
    //  consider that ordinal x are subdividing the cell into subcells anyway
    encoding.forEach(function(encType, field) {

      if (encType !== ROW && encType !== COL &&
          !((encType === X || encType === Y) &&
          vlfield.isOrdinalScale(field, Encoding.isType))
        ) {
        numPoints *= encoding.cardinality(encType, stats);
      }
    });

  } else { // raw plot
    numPoints = stats.count;

    // small multiples divide number of points
    var numMultiples = 1;
    if (encoding.has(ROW)) {
      numMultiples *= encoding.cardinality(ROW, stats);
    }
    if (encoding.has(COL)) {
      numMultiples *= encoding.cardinality(COL, stats);
    }
    numPoints /= numMultiples;
  }

  var opacity = 0;
  if (numPoints < 20) {
    opacity = 1;
  } else if (numPoints < 200) {
    opacity = 0.7;
  } else if (numPoints < 1000) {
    opacity = 0.6;
  } else {
    opacity = 0.3;
  }

  return opacity;
}

<|endoftext|>


<commit_before><commit_msg>throw errors if a message has any recipients that are not invalid, to prevent accidental privacy leaks<commit_after><|endoftext|>


<commit_before>// Copyright (c) 2015, Evan Summers (twitter.com/evanxsummers)
// ISC license, see http://github.com/evanx/redexutil/LICENSE

// a more explicit promisification for redis client

// also see more automated attempt using es6-promisify:
// https://github.com/evanx/redex/blob/master/lib/redisPromisified.js

import redis from 'redis';

import Files from './Files';
import Loggers from './Loggers';

const logger = Loggers.create(module.filename, 'info');

const state = {
   count: 0,
   clients: new Set()
};

function selectPromise(client, dbNumber) {
   return new Promise((resolve, reject) => {
      client.select(dbNumber, err => {
         if (err) {
            logger.error('select', this.dbNumber);
            reject(err);
         } else {
            logger.info('select', this.dbNumber);
            resolve();
         }
      });
   });
}

function createClient(options) {
   state.count++;
   let redisClient = redis.createClient(options || {});
   state.clients.add(redisClient);
   redisClient.on('error', err => {
      logger.error('redis error:', err);
   });
   if (options.dbNumber) {
      selectPromise(redisClient, options.dbNumber);
   }
   return redisClient;
}

function createCallback(resolve, reject) {
   return (err, reply) => {
      if (err) {
         logger.debug('redis reject error:', err);
         reject(err);
      } else {
         logger.debug('redis resolve:', reply);
         resolve(reply);
      }
   };
}

function createPromise(fn) {
   return new Promise((resolve, reject) => fn(createCallback(resolve, reject)));
}

export default class Redis {

   // @param client
   // client maybe null e.g. if constructed with empty options object i.e. {}
   // if no options, then client is created

   constructor(options) {
      if (options) {
         if (lodash.isString(options)) {
            this.source = Files.basename(options);
            this.client = createClient(options);
            logger.info('construct', state.count, this.source);
         } else if (options === {}) {
            logger.info('construct defer');
         } else if (options.client) {
            this.client = options.client;
            logger.info('construct wrapper');
         } else if (options.source) {
            this.source = Files.basename(options.source);
            this.client = createClient(options);
            logger.info('construct', state.count, this.source);
         } else if (options.return_buffers || options.dbNumber) {
            this.client = createClient(options);
         } else {
            throw 'Invalid options: ' + options.toString();
         }
      } else {
         this.client = createClient();
         logger.info('construct', state.count);
      }
      if (this.client && this.dbNumber) {
         selectPromise(this.client, this.dbNumber);
      }
   }

   init(options) {
      if (!this.client) {
         this.client = createClient(options);
      }
      if (options.dbNumber) {
         selectPromise(this.client, options.dbNumber);
      } else if (this.dbNumber) {
         selectPromise(this.client, this.dbNumber);
      }
   }

   select(dbNumber) {
      return createPromise(cb => this.client.select(dbNumber, cb));
   }

   end() {
      if (this.client) {
         if (state.clients.size > 0) {
            state.clients.delete(this.client);
            logger.info('end', state.clients.size, this.source);
         } else {
            logger.error('end');
         }
         this.client.end();
         this.client = null;
      }
   }

   time() {
      return createPromise(cb => this.client.time(cb));
   }

   timeSeconds() {
      return this.time().then(time => {
         return parseInt(time[0]);
      });
   }

   set(key, value) {
      //logger.debug('set', key, value);
      return createPromise(cb => this.client.set(key, value, cb));
   }

   get(key) {
      return createPromise(cb => this.client.get(key, cb));
   }

   mget(keys) {
      return createPromise(cb => this.client.mget(keys, cb));
   }

   del(key) {
      return createPromise(cb => this.client.del(key, cb));
   }

   expire(key, seconds) {
      return createPromise(cb => this.client.expire(key, seconds, cb));
   }

   sadd(key, member) {
      //logger.debug('sadd', key, member);
      return createPromise(cb => this.client.sadd(key, member, cb));
   }

   sismember(key, member) {
      return createPromise(cb => this.client.sismember(key, member, cb));
   }

   smembers(key, member) {
      return createPromise(cb => this.client.smembers(key, cb));
   }

   srem(key, member) {
      return createPromise(cb => this.client.srem(key, member, cb));
   }

   scard(key) {
      return createPromise(cb => this.client.scard(key, cb));
   }

   hset(key, field, value) {
      return createPromise(cb => this.client.hset(key, field, value, cb));
   }

   hmset(key, value) {
      return createPromise(cb => this.client.hmset(key, value, cb));
   }

   hget(key, field) {
      return createPromise(cb => this.client.hget(key, field, cb));
   }

   hgetall(key) {
      return createPromise(cb => this.client.hgetall(key, cb));
   }

   lindex(key, index) {
      return createPromise(cb => this.client.lindex(key, index, cb));
   }

   lset(key, index, value) {
      return createPromise(cb => this.client.lset(key, index, value, cb));
   }

   llen(key) {
      return createPromise(cb => this.client.llen(key, cb));
   }

   ltrim(key, start, stop) {
      return createPromise(cb => this.client.ltrim(key, start, stop, cb));
   }

   lrange(key, start, stop) {
      return createPromise(cb => this.client.lrange(key, start, stop, cb));
   }

   lpush(key, value) {
      return createPromise(cb => this.client.lpush(key, value, cb));
   }

   rpush(key, value) {
      return createPromise(cb => this.client.rpush(key, value, cb));
   }

   lpop(key) {
      return createPromise(cb => this.client.lpop(key, cb));
   }

   rpop(key) {
      return createPromise(cb => this.client.rpop(key, cb));
   }

   blpop(key, timeout) {
      return createPromise(cb => this.client.blpop(key, timeout, cb));
   }

   brpop(key, timeout) {
      return createPromise(cb => this.client.brpop(key, timeout, cb));
   }

   rpoplpush(source, destination) {
      return createPromise(cb => this.client.rpoplpush(source, destination, cb));
   }

   brpoplpush(source, destination, timeout) {
      return createPromise(cb => this.client.brpoplpush(source, destination, timeout, cb));
   }

   zrange(key, start, stop) {
      return createPromise(cb => this.client.zrange(key, start, stop, cb));
   }

   zrevrange(key, start, stop) {
      return createPromise(cb => this.client.zrevrange(key, start, stop, cb));
   }

   // TODO all the rest, see https://github.com/mranney/node_redis/blob/master/lib/commands.js

   multi() {
      let multi = this.client.multi();
      multi.execCallback = multi.exec;
      multi.execPromise = () => {
         return createPromise(cb => multi.execCallback(cb));
      };
      multi.exec = multi.execPromise;
      return multi;
   }

}

// see test: https://github.com/evanx/redex/blob/master/test/redisPromised.js
<commit_msg>dbNumber<commit_after>// Copyright (c) 2015, Evan Summers (twitter.com/evanxsummers)
// ISC license, see http://github.com/evanx/redexutil/LICENSE

// a more explicit promisification for redis client

// also see more automated attempt using es6-promisify:
// https://github.com/evanx/redex/blob/master/lib/redisPromisified.js

import redis from 'redis';

import Files from './Files';
import Loggers from './Loggers';

const logger = Loggers.create(module.filename, 'info');

const state = {
   count: 0,
   clients: new Set()
};

function selectPromise(client, dbNumber) {
   return new Promise((resolve, reject) => {
      client.select(dbNumber, err => {
         if (err) {
            logger.error('select', this.dbNumber);
            reject(err);
         } else {
            logger.info('select', this.dbNumber);
            resolve();
         }
      });
   });
}

function createClient(options) {
   state.count++;
   let redisClient = redis.createClient(options || {});
   state.clients.add(redisClient);
   redisClient.on('error', err => {
      logger.error('redis error:', err);
   });
   if (options.dbNumber) {
      selectPromise(redisClient, options.dbNumber);
   }
   return redisClient;
}

function createCallback(resolve, reject) {
   return (err, reply) => {
      if (err) {
         logger.debug('redis reject error:', err);
         reject(err);
      } else {
         logger.debug('redis resolve:', reply);
         resolve(reply);
      }
   };
}

function createPromise(fn) {
   return new Promise((resolve, reject) => fn(createCallback(resolve, reject)));
}

export default class Redis {

   // @param client
   // client maybe null e.g. if constructed with empty options object i.e. {}
   // if no options, then client is created

   constructor(options) {
      if (options) {
         if (lodash.isString(options)) {
            this.source = Files.basename(options);
            this.client = createClient(options);
            logger.info('construct', state.count, this.source);
         } else if (options === {}) {
            logger.info('construct defer');
         } else if (options.client) {
            this.client = options.client;
            logger.info('construct wrapper');
         } else if (options.source) {
            this.source = Files.basename(options.source);
            this.client = createClient(options);
            logger.info('construct', state.count, this.source);
         } else if (options.return_buffers || options.dbNumber) {
            this.client = createClient(options);
         } else {
            throw 'Invalid options: ' + options.toString();
         }
      } else {
         this.client = createClient();
         logger.info('construct', state.count);
      }
      if (this.client && this.dbNumber) {
         selectPromise(this.client, this.dbNumber);
      }
   }

   init(options) {
      if (!this.client) {
         this.client = createClient(options);
      }
      if (options && options.dbNumber) {
         selectPromise(this.client, options.dbNumber);
      } else if (this.dbNumber) {
         selectPromise(this.client, this.dbNumber);
      }
   }

   select(dbNumber) {
      return createPromise(cb => this.client.select(dbNumber, cb));
   }

   end() {
      if (this.client) {
         if (state.clients.size > 0) {
            state.clients.delete(this.client);
            logger.info('end', state.clients.size, this.source);
         } else {
            logger.error('end');
         }
         this.client.end();
         this.client = null;
      }
   }

   time() {
      return createPromise(cb => this.client.time(cb));
   }

   timeSeconds() {
      return this.time().then(time => {
         return parseInt(time[0]);
      });
   }

   set(key, value) {
      //logger.debug('set', key, value);
      return createPromise(cb => this.client.set(key, value, cb));
   }

   get(key) {
      return createPromise(cb => this.client.get(key, cb));
   }

   mget(keys) {
      return createPromise(cb => this.client.mget(keys, cb));
   }

   del(key) {
      return createPromise(cb => this.client.del(key, cb));
   }

   expire(key, seconds) {
      return createPromise(cb => this.client.expire(key, seconds, cb));
   }

   sadd(key, member) {
      //logger.debug('sadd', key, member);
      return createPromise(cb => this.client.sadd(key, member, cb));
   }

   sismember(key, member) {
      return createPromise(cb => this.client.sismember(key, member, cb));
   }

   smembers(key, member) {
      return createPromise(cb => this.client.smembers(key, cb));
   }

   srem(key, member) {
      return createPromise(cb => this.client.srem(key, member, cb));
   }

   scard(key) {
      return createPromise(cb => this.client.scard(key, cb));
   }

   hset(key, field, value) {
      return createPromise(cb => this.client.hset(key, field, value, cb));
   }

   hmset(key, value) {
      return createPromise(cb => this.client.hmset(key, value, cb));
   }

   hget(key, field) {
      return createPromise(cb => this.client.hget(key, field, cb));
   }

   hgetall(key) {
      return createPromise(cb => this.client.hgetall(key, cb));
   }

   lindex(key, index) {
      return createPromise(cb => this.client.lindex(key, index, cb));
   }

   lset(key, index, value) {
      return createPromise(cb => this.client.lset(key, index, value, cb));
   }

   llen(key) {
      return createPromise(cb => this.client.llen(key, cb));
   }

   ltrim(key, start, stop) {
      return createPromise(cb => this.client.ltrim(key, start, stop, cb));
   }

   lrange(key, start, stop) {
      return createPromise(cb => this.client.lrange(key, start, stop, cb));
   }

   lpush(key, value) {
      return createPromise(cb => this.client.lpush(key, value, cb));
   }

   rpush(key, value) {
      return createPromise(cb => this.client.rpush(key, value, cb));
   }

   lpop(key) {
      return createPromise(cb => this.client.lpop(key, cb));
   }

   rpop(key) {
      return createPromise(cb => this.client.rpop(key, cb));
   }

   blpop(key, timeout) {
      return createPromise(cb => this.client.blpop(key, timeout, cb));
   }

   brpop(key, timeout) {
      return createPromise(cb => this.client.brpop(key, timeout, cb));
   }

   rpoplpush(source, destination) {
      return createPromise(cb => this.client.rpoplpush(source, destination, cb));
   }

   brpoplpush(source, destination, timeout) {
      return createPromise(cb => this.client.brpoplpush(source, destination, timeout, cb));
   }

   zrange(key, start, stop) {
      return createPromise(cb => this.client.zrange(key, start, stop, cb));
   }

   zrevrange(key, start, stop) {
      return createPromise(cb => this.client.zrevrange(key, start, stop, cb));
   }

   // TODO all the rest, see https://github.com/mranney/node_redis/blob/master/lib/commands.js

   multi() {
      let multi = this.client.multi();
      multi.execCallback = multi.exec;
      multi.execPromise = () => {
         return createPromise(cb => multi.execCallback(cb));
      };
      multi.exec = multi.execPromise;
      return multi;
   }

}

// see test: https://github.com/evanx/redex/blob/master/test/redisPromised.js
<|endoftext|>


<commit_before>vjs.plugin('vastClient', function VASTPlugin(options) {
  var player = this;
  var vast = new VASTClient();
  var adsCanceled = false;

  var defaultOpts = {
    // maximum amount of time in ms to wait to receive `adsready` from the ad
    // implementation after play has been requested. Ad implementations are
    // expected to load any dynamic libraries and make any requests to determine
    // ad policies for a video during this time.
    timeout: 500,

    // for the moment we don't support post roll ads
    postrollTimeout: 0,

    // maximun amount of time for the ad to actually start playing. If this timeout gets
    // triggered the ads will be cancelled
    adCancelTimeout: 3000,

    // Boolean flag that configures the player to play a new ad before the user sees the video again
    // the current video
    playAdAlways: false,

    // Flag to enable or disable the ads by default.
    adsEnabled: true
  };

  var settings = extend({}, defaultOpts, options || {});

  if(!settings.prerollTimeout) {
    settings.prerollTimeout = settings.adCancelTimeout;
  }

  if (isString(settings.url)) {
    settings.url = echoFn(settings.url);
  }

  if (!isDefined(settings.url)) {
    return trackAdError(new VASTError('on VideoJS VAST plugin, missing url on options object'));
  }

  player.on('play', playAdHandler);
  player.on('readyforpreroll', playPrerollAd);

  // initialize videojs contrib ads plugin
  player.ads(settings);

  player.vast = {
    isEnabled: function () {
      return settings.adsEnabled;
    },

    enable: function () {
      settings.adsEnabled = true;
    },

    disable: function () {
      settings.adsEnabled = false;
    }
  };

  return player.vast;

  /**** Local functions ****/
  function playAdHandler() {
    if(settings.adsEnabled){
      if(player.ads.state === 'content-set' && canPlayPrerollAd()){
        initAds();
      }
    }else{
      cancelAds();
    }

    /*** Local functions ***/
    function canPlayPrerollAd(){
      //TODO: add the preroll delay to the options and remove this substraction
      var allowedPrerollDelay = settings.adCancelTimeout - settings.prerollTimeout;
      return player.currentTime() <= allowedPrerollDelay;
    }

    function initAds() {
      var adCancelTimeoutId;
      adsCanceled = false;

      if(player.paused()){
        player.pause();
      }

      player.trigger('adsready');
      addSpinnerIcon();
      player.on('vast.adstart', removeSpinnerIcon);
      player.on('vast.aderror', removeSpinnerIcon);

      adCancelTimeoutId = setTimeout(function () {
        trackAdError(new VASTError('timeout while waiting for the video to start playing', 402));
      }, settings.adCancelTimeout);

      player.one('vast.adstart', clearAdCancelTimeout);
      player.one('vast.aderror', clearAdCancelTimeout);

      /*** local functions ***/
      function clearAdCancelTimeout(){
        if(adCancelTimeoutId) {
          clearTimeout(adCancelTimeoutId);
          adCancelTimeoutId = null;
          player.off('vast.adstart', clearAdCancelTimeout);
          player.off('vast.aderror', clearAdCancelTimeout);
        }
      }
    }

    function addSpinnerIcon() {
      dom.addClass(player.el(), 'vjs-vast-ad-loading');
    }

    function removeSpinnerIcon() {
      //IMPORTANT NOTE: We remove the spinnerIcon asynchronously to give time to the browser to start the video.
      // If we remove it synchronously we see a flash of the content video before the ad starts playing.
      setTimeout(function() {
        dom.removeClass(player.el(), 'vjs-vast-ad-loading');
        player.off('vast.adstart', removeSpinnerIcon);
        player.off('vast.aderror', removeSpinnerIcon);
      }, 100);
    }

  }

  function cancelAds() {
    // We trigger 'adscanceled' to cancel the ads if they are in 'content-set' or 'ads-read?' state
    player.trigger('adscanceled');
    //We trigger 'adserror' to cancel the ads if they are in 'adsready' or 'preroll?' or 'ad-playback' state
    player.trigger('adserror');
    adsCanceled = true;
  }

  function playPrerollAd() {
    async.waterfall([
      getVastResponse,
      playAd,
      finishPlayingAd
    ], function (error, response) {
      if (error) {
        trackAdError(error, response);
      }

      if (settings.playAdAlways) {
        // No matter what happens we play a new ad before the user sees the video again.
        player.one('contentended', function () {

          setTimeout(function () {
            player.trigger('contentupdate');
          }, 0);
        });
      }
    });
  }

  function getVastResponse(callback) {
    vast.getVASTResponse(settings.url(), callback);
  }

  function playAd(vastResponse, callback) {
    //If the state is not 'preroll?' it means the ads were canceled therefore, we break the waterfall
    if(adsCanceled){
      return;
    }

    var adIntegrator = isVPAID(vastResponse) ? new VPAIDIntegrator(player) : new VASTIntegrator(player);
    player.ads.startLinearAdMode();
    adIntegrator.playAd(vastResponse, callback);
    player.one('vast.adstart', function () {
      player.controlBar.addChild('AdsLabel');
      player.one('adended', removeAdsLabel);
      player.one('adserror', removeAdsLabel);
    });

    preventManualProgress();

    /*** Local functions ****/
    function removeAdsLabel() {
      player.controlBar.removeChild('AdsLabel');
    }

    function preventManualProgress(){
      var PROGRESS_THRESHOLD = 0.5;
      var previousTime = player.currentTime();
      var tech = player.el().querySelector('.vjs-tech');
      var skipad_attempts = 0;

      player.on('adtimeupdate', adTimeupdateHandler);
      player.one('adended', function () {
        player.off('adtimeupdate', adTimeupdateHandler);
      });

      /*** Local functions ***/
      function adTimeupdateHandler() {
        var currentTime = player.currentTime();
        var progressDelta = Math.abs(currentTime - previousTime);
        if (progressDelta > PROGRESS_THRESHOLD) {
          skipad_attempts+=1;
          if(skipad_attempts >= 2){
            player.pause();
          }
          player.currentTime(previousTime);
        } else {
          previousTime = currentTime;
        }
      }
    }
  }

  function finishPlayingAd(vastResponse, callback) {
    player.ads.endLinearAdMode();
    player.controlBar.removeChild('AdsLabel');
    callback(null, vastResponse);
  }

  function trackAdError(error, vastResponse) {
    player.trigger({type: 'vast.aderror', error: error});
    cancelAds();
    if (console && console.log) {
      console.log('AD ERROR:', error.message, error, vastResponse);
    }
  }

  function isVPAID(vastResponse) {
    var i, len;
    var mediaFiles = vastResponse.mediaFiles;
    for (i = 0, len = mediaFiles.length; i < len; i++) {
      if (vastUtil.isVPAID(mediaFiles[i])) {
        return true;
      }
    }
    return false;
  }
});
<commit_msg>Added TODO comment<commit_after>vjs.plugin('vastClient', function VASTPlugin(options) {
  var player = this;
  var vast = new VASTClient();
  var adsCanceled = false;

  var defaultOpts = {
    // maximum amount of time in ms to wait to receive `adsready` from the ad
    // implementation after play has been requested. Ad implementations are
    // expected to load any dynamic libraries and make any requests to determine
    // ad policies for a video during this time.
    timeout: 500,

    // for the moment we don't support post roll ads
    postrollTimeout: 0,

    // maximun amount of time for the ad to actually start playing. If this timeout gets
    // triggered the ads will be cancelled
    adCancelTimeout: 3000,

    // Boolean flag that configures the player to play a new ad before the user sees the video again
    // the current video
    playAdAlways: false,

    // Flag to enable or disable the ads by default.
    adsEnabled: true
  };

  var settings = extend({}, defaultOpts, options || {});

  if(!settings.prerollTimeout) {
    settings.prerollTimeout = settings.adCancelTimeout;
  }

  if (isString(settings.url)) {
    settings.url = echoFn(settings.url);
  }

  if (!isDefined(settings.url)) {
    return trackAdError(new VASTError('on VideoJS VAST plugin, missing url on options object'));
  }

  player.on('play', playAdHandler);
  player.on('readyforpreroll', playPrerollAd);

  // initialize videojs contrib ads plugin
  player.ads(settings);

  player.vast = {
    isEnabled: function () {
      return settings.adsEnabled;
    },

    enable: function () {
      settings.adsEnabled = true;
    },

    disable: function () {
      settings.adsEnabled = false;
    }
  };

  return player.vast;

  /**** Local functions ****/
  function playAdHandler() {
    if(settings.adsEnabled){
      if(player.ads.state === 'content-set' && canPlayPrerollAd()){
        initAds();
      }
    }else{
      cancelAds();
    }

    /*** Local functions ***/
    function canPlayPrerollAd(){
      //TODO: add the preroll delay to the options and remove this substraction
      var allowedPrerollDelay = settings.adCancelTimeout - settings.prerollTimeout;
      return player.currentTime() <= allowedPrerollDelay;
    }

    function initAds() {
      var adCancelTimeoutId;
      adsCanceled = false;

      if(player.paused()){
        player.pause();
      }

      player.trigger('adsready');
      addSpinnerIcon();
      player.on('vast.adstart', removeSpinnerIcon);
      player.on('vast.aderror', removeSpinnerIcon);

      adCancelTimeoutId = setTimeout(function () {
        trackAdError(new VASTError('timeout while waiting for the video to start playing', 402));
      }, settings.adCancelTimeout);

      player.one('vast.adstart', clearAdCancelTimeout);
      player.one('vast.aderror', clearAdCancelTimeout);

      /*** local functions ***/
      function clearAdCancelTimeout(){
        if(adCancelTimeoutId) {
          clearTimeout(adCancelTimeoutId);
          adCancelTimeoutId = null;
          player.off('vast.adstart', clearAdCancelTimeout);
          player.off('vast.aderror', clearAdCancelTimeout);
        }
      }
    }

    function addSpinnerIcon() {
      dom.addClass(player.el(), 'vjs-vast-ad-loading');
    }

    function removeSpinnerIcon() {
      //IMPORTANT NOTE: We remove the spinnerIcon asynchronously to give time to the browser to start the video.
      // If we remove it synchronously we see a flash of the content video before the ad starts playing.
      setTimeout(function() {
        dom.removeClass(player.el(), 'vjs-vast-ad-loading');
        player.off('vast.adstart', removeSpinnerIcon);
        player.off('vast.aderror', removeSpinnerIcon);
      }, 100);
    }

  }

  function cancelAds() {
    // We trigger 'adscanceled' to cancel the ads if they are in 'content-set' or 'ads-read?' state
    player.trigger('adscanceled');
    //We trigger 'adserror' to cancel the ads if they are in 'adsready' or 'preroll?' or 'ad-playback' state
    player.trigger('adserror');
    adsCanceled = true;
  }

  function playPrerollAd() {
    async.waterfall([
      getVastResponse,
      playAd,
      finishPlayingAd
    ], function (error, response) {
      if (error) {
        trackAdError(error, response);
      }

      if (settings.playAdAlways) {
        // No matter what happens we play a new ad before the user sees the video again.
        player.one('contentended', function () {

          setTimeout(function () {
            player.trigger('contentupdate');
          }, 0);
        });
      }
    });
  }

  function getVastResponse(callback) {
    vast.getVASTResponse(settings.url(), callback);
  }

  function playAd(vastResponse, callback) {
    //TODO: Find a better way to stop the play. The 'playPrerollWaterfall' ends in an inconsistent situation
    //If the state is not 'preroll?' it means the ads were canceled therefore, we break the waterfall
    if(adsCanceled){
      return;
    }

    var adIntegrator = isVPAID(vastResponse) ? new VPAIDIntegrator(player) : new VASTIntegrator(player);
    player.ads.startLinearAdMode();
    adIntegrator.playAd(vastResponse, callback);
    player.one('vast.adstart', function () {
      player.controlBar.addChild('AdsLabel');
      player.one('adended', removeAdsLabel);
      player.one('adserror', removeAdsLabel);
    });

    preventManualProgress();

    /*** Local functions ****/
    function removeAdsLabel() {
      player.controlBar.removeChild('AdsLabel');
    }

    function preventManualProgress(){
      var PROGRESS_THRESHOLD = 0.5;
      var previousTime = player.currentTime();
      var tech = player.el().querySelector('.vjs-tech');
      var skipad_attempts = 0;

      player.on('adtimeupdate', adTimeupdateHandler);
      player.one('adended', function () {
        player.off('adtimeupdate', adTimeupdateHandler);
      });

      /*** Local functions ***/
      function adTimeupdateHandler() {
        var currentTime = player.currentTime();
        var progressDelta = Math.abs(currentTime - previousTime);
        if (progressDelta > PROGRESS_THRESHOLD) {
          skipad_attempts+=1;
          if(skipad_attempts >= 2){
            player.pause();
          }
          player.currentTime(previousTime);
        } else {
          previousTime = currentTime;
        }
      }
    }
  }

  function finishPlayingAd(vastResponse, callback) {
    player.ads.endLinearAdMode();
    player.controlBar.removeChild('AdsLabel');
    callback(null, vastResponse);
  }

  function trackAdError(error, vastResponse) {
    player.trigger({type: 'vast.aderror', error: error});
    cancelAds();
    if (console && console.log) {
      console.log('AD ERROR:', error.message, error, vastResponse);
    }
  }

  function isVPAID(vastResponse) {
    var i, len;
    var mediaFiles = vastResponse.mediaFiles;
    for (i = 0, len = mediaFiles.length; i < len; i++) {
      if (vastUtil.isVPAID(mediaFiles[i])) {
        return true;
      }
    }
    return false;
  }
});
<|endoftext|>


<commit_before>/*
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * 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.
 */

/**
  Listeners are high-level pre-bound event call-backs.

  Ex.
  foam.CLASS({
    name: 'Sprinkler',
    listeners: [

      // short-form
      function onAlarm() { ... },

      // long-form
      {
        name: 'onClear',
        isFramed: true,
        code: function() { ... }
      }
    ]
  });

  You might use the above onAlarm listener like this:
  alarm.ring.sub(sprinker.onAlarm);

  Notice, that normally JS methods forget which object they belong
  to so you would need to do something like:

alarm.ring.sub(sprinker.onAlarm.bind(sprinkler));

  But listeners are pre-bound.
*/
foam.CLASS({
  package: 'foam.core',
  name: 'Listener',

  properties: [
    { name: 'name', required: true },
    { name: 'code', required: true },
    { class: 'Boolean', name: 'isFramed',   value: false },
    { class: 'Boolean', name: 'isMerged',   value: false },
    { class: 'Int',     name: 'mergeDelay', value: 16, units: 'ms' }
  ],

  methods: [
    function installInProto(proto) {
      var name       = this.name;
      var code       = this.code;
      var isMerged   = this.isMerged;
      var isFramed   = this.isFramed;
      var mergeDelay = this.mergeDelay;

      Object.defineProperty(proto, name, {
        get: function topicGetter() {
          if ( ! this.hasOwnPrivate_(name) ) {
            var self = this;
            var l = function(sub) {
              if ( self.isDestroyed() ) {
                if ( sub ) {
                  console.warn('Destroying stale subscription for', self.cls_.id);
                  sub.destroy();
                }
              } else {
                code.apply(self, arguments);
              }
            };

            foam.Function.setName(l, name);

            if ( isMerged ) {
              l = this.__context__.merged(l, mergeDelay);
            } else if ( isFramed ) {
              l = this.__context__.framed(l);
            }
            this.setPrivate_(name, l);
          }

          return this.getPrivate_(name);
        },
        configurable: true,
        enumerable: false
      });
    }
  ]
});

foam.CLASS({
  refines: 'foam.core.Model',
  properties: [
    {
      class: 'AxiomArray',
      of: 'Listener',
      name: 'listeners',
      adaptArrayElement: function(o) {
        if ( typeof o === 'function' ) {
          console.assert(o.name, 'Listener must be named');
          return foam.core.Listener.create({name: o.name, code: o});
        }
        return foam.core.Listener.isInstance(o) ?
            o :
            foam.core.Listener.create(o) ;
      }
    }
  ]
});
<commit_msg>Formatting.<commit_after>/*
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * 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.
 */

/**
  Listeners are high-level pre-bound event call-backs.

  Ex.
  foam.CLASS({
    name: 'Sprinkler',
    listeners: [

      // short-form
      function onAlarm() { ... },

      // long-form
      {
        name: 'onClear',
        isFramed: true,
        code: function() { ... }
      }
    ]
  });

  You might use the above onAlarm listener like this:
  alarm.ring.sub(sprinker.onAlarm);

  Notice, that normally JS methods forget which object they belong
  to so you would need to do something like:

alarm.ring.sub(sprinker.onAlarm.bind(sprinkler));

  But listeners are pre-bound.
*/
foam.CLASS({
  package: 'foam.core',
  name: 'Listener',

  properties: [
    { name: 'name', required: true },
    { name: 'code', required: true },
    { class: 'Boolean', name: 'isFramed',   value: false },
    { class: 'Boolean', name: 'isMerged',   value: false },
    { class: 'Int',     name: 'mergeDelay', value: 16, units: 'ms' }
  ],

  methods: [
    function installInProto(proto) {
      var name       = this.name;
      var code       = this.code;
      var isMerged   = this.isMerged;
      var isFramed   = this.isFramed;
      var mergeDelay = this.mergeDelay;

      Object.defineProperty(proto, name, {
        get: function topicGetter() {
          if ( ! this.hasOwnPrivate_(name) ) {
            var self = this;
            var l = function(sub) {
              if ( self.isDestroyed() ) {
                if ( sub ) {
                  console.warn('Destroying stale subscription for', self.cls_.id);
                  sub.destroy();
                }
              } else {
                code.apply(self, arguments);
              }
            };

            foam.Function.setName(l, name);

            if ( isMerged ) {
              l = this.__context__.merged(l, mergeDelay);
            } else if ( isFramed ) {
              l = this.__context__.framed(l);
            }
            this.setPrivate_(name, l);
          }

          return this.getPrivate_(name);
        },
        configurable: true,
        enumerable: false
      });
    }
  ]
});

foam.CLASS({
  refines: 'foam.core.Model',

  properties: [
    {
      class: 'AxiomArray',
      of: 'Listener',
      name: 'listeners',
      adaptArrayElement: function(o) {
        if ( typeof o === 'function' ) {
          console.assert(o.name, 'Listener must be named');
          return foam.core.Listener.create({name: o.name, code: o});
        }

        return foam.core.Listener.isInstance(o) ?
            o :
            foam.core.Listener.create(o) ;
      }
    }
  ]
});
<|endoftext|>


<commit_before>/*
==================================================================================================
  Core - JavaScript Foundation
  Copyright 2010-2012 Zynga Inc.
==================================================================================================
*/

(function(global)
{
	// Internal data storage
	var deployed, root, assets;

	/**
	 * {Array} Resolves the given @id {String} into the stored entry of the asset data base.
	 */
	var resolve = function(id)
	{
		if (id.constructor == Object) {
			return id;
		}

		if (core.Env.isSet("debug")) {
			core.Assert.isType(id, "String");
		}

		var splits = id.split("/");
		var current = assets;
		for (var i=0, l=splits.length; current&&i<l; i++) {
			current = current[splits[i]];
		}

		if (current == null) {
			console.warn("Could not resolve URI: " + id);
		}

		return current;
	};

	/**
	 * {Map} Collects asset with the given asset @prefix {String} from the given @section {Map}.
	 * Optionally works recursively by enabling @recursive {Boolean?false} or only
	 * returns the data by enabling @data {Boolean?false}. Last parameter @all {Map?}
	 * is used to fill an existing object instead of a new one.
	 */
	var collect = function(prefix, cut, section, recursive, data, all)
	{
		if (!all) {
			all = {};
		}

		if (section)
		{
			for (var filename in section)
			{
				var entry = section[filename];
				var id = prefix + "/" + filename;

				if (entry.constructor == Object)
				{
					if (recursive) {
						collect(id, cut, entry, recursive, data, all);
					}
				}
				else
				{
					var localId = !cut ? id : id.slice(cut);

					if (data) {
						all[localId] = entry;
					} else if (deployed) {
						all[localId] = root + id;
					} else {
						all[localId] = root + entry[0];
					}
				}
			}
		}

		return all;
	};

	/**
	 * {String} Converts the given asset @id {String} to a full qualified URI.
	 * The method throws an error whenever an asset ID is unknown.
	 */
	var toUri = function(id)
	{
		if (core.Env.isSet("debug")) {
			core.Assert.isType(id, "String");
		}

		if (id.charAt("0") == "/" || id.startsWith("http:") || id.startsWith("https:")) {
			return id;
		}

		if (deployed)
		{
			return root + id;
		}
		else
		{
			var entry = resolve(id);
			if (core.Env.isSet("debug") && !entry) {
				throw new Error("Can't resolve URL for asset: " + id);
			}

			return root + entry[entry.length-1];
		}
	};

	/**
	 * Merged two data maps @src {Map} and @dst {Map} recursively by using as much of
	 * the original data as possible (no copying). Will never override existing values!
	 */
	var mergeData = function(src, dst)
	{
		for (var key in src)
		{
			var srcValue = src[key];
			var dstValue = dst[key];

			// Just use value from source (works for folders and files)
			if (dstValue == null) {
				dst[key] = srcValue;
			}

			// Deep merge
			else if (srcValue.constructor === Object && dstValue.construtor === Object) {
				mergeData(srcValue, dstValue);
			}
		}
	};

	var getFrameNumber = function(entry)
	{
		// Correct entry length for format detection
		var length = entry.length;
		if (!deployed) {
			length--;
		}

		switch(length)
		{
			case 4:
				// auto calculated frame size
				return entry[2] * entry[3];

			case 5:
				// manually defined frame size
				return entry[4];

			case 6:
				// manually defined frames
				return entry[5].length;

			case 7:
				// auto calculated frame size (image sprite)
				return entry[5] * entry[6];

			case 8:
				// manually defined frame size (image sprite)
				return entry[7];
		}

		return 1;
	};

	/**
	 * Contains information about images (size, format, clipping, ...) and
	 * other assets like CSS files, local data, ...
	 *
	 * #require(ext.sugar.Array)
	 * #require(ext.sugar.Object)
	 * #require(ext.sugar.String)
	 */
	core.Module("core.io.Asset",
	{
		reset : function()
		{
			deployed = root = null;
			assets = {};
		},

		isDeployed: function(data) {
			return deployed;
		},

		add : function(data)
		{
			// Validate input data
			if (core.Env.isSet("debug"))
			{
				core.Assert.isType(data, "Map");
				core.Assert.isType(data.assets, "Map");
				core.Assert.isType(data.root, "String");
				core.Assert.isType(data.deployed, "Boolean");
			}

			// Initial data
			if (root == null)
			{
				assets = data.assets;
				deployed = data.deployed;
				root = data.root;
			}

			// Inject data
			else
			{
				if (core.Env.isSet("debug"))
				{
					if (data.deployed != deployed) {
						throw new Error("Cannot handle deployed and undeployed assets into one data set!");
					}

					if (data.root != root) {
						throw new Error("Cannot handle two different roots in on data set!");
					}
				}

				mergeData(data.assets, assets);
			}

		},

		toUri : toUri,
		resolve: resolve,

		/**
		 * {Boolean} Whether the registry has information about the given asset @id {String}.
		 */
		has : function(id)
		{
			if (core.Env.isSet("debug")) {
				core.Assert.isType(id, "String");
			}

			return !!resolve(id);
		},

		/**
		 * Loads the given @section {String}. Optionally @recursive {Boolean?false} it traverses
		 * the whole tree starting at @section. The optional @callback {Function?} is executed
		 * in the given @context {Object?} when all files are loaded. It is called with one
		 * parameter which contains are `Map` of all data (relative asset ID to loaded item).
		 * Optionally caching can be disabled by attaching a random `GET` parameter.
		 */
		loadSection: function(section, recursive, callback, context, nocache)
		{
			if (core.Env.isSet("debug"))
			{
				core.Assert.isType(section, "String");

				if (section == "") {
					throw new Error("Invalid section: " + section);
				}

				if (section.endsWith("/")) {
					throw new Error("Sections must not end with a slash!")
				}

				if (recursive != null) {
					core.Assert.isType(recursive, "Boolean");
				}

				if (callback != null) {
					core.Assert.isType(callback, "Function");
				}

				if (context != null) {
					core.Assert.isType(context, "Object");
				}

				if (nocache != null) {
					core.Assert.isType(nocache, "Boolean");
				}
			}

			// Collect assets
			var all = collect(section, section.length + 1, resolve(section), recursive);
			var uris = Object.values(all);

			// Build back tables to translate uris back to local IDs
			var urisToIds = uris.zip(Object.keys(all));

			var helper = callback ? function(data) {
				callback.call(context||global, Object.translate(data, urisToIds));
			} : callback;

			core.io.Queue.load(uris, helper, this, nocache);
		},

		/**
		 * Loads the given assets by their ID and executes @callback {Function?}
		 * in the given @context {Object?global}. Optionally caching can be disabled
		 * by attaching a random `GET` parameter.
		 */
		load: function(ids, callback, context, nocache)
		{
			if (core.Env.isSet("debug"))
			{
				core.Assert.isType(ids, "Array");

				if (callback != null) {
					core.Assert.isType(callback, "Function");
				}

				if (context != null) {
					core.Assert.isType(context, "Object");
				}

				if (nocache != null) {
					core.Assert.isType(nocache, "Boolean");
				}
			}

			var uris = ids.map(toUri);
			var urisToIds = uris.zip(ids);

			var helper = callback ? function(data) {
				callback.call(context||global, Object.translate(data, urisToIds));
			} : callback;

			core.io.Queue.load(uris, helper, this, nocache);
		},

		//
		// Data formats for image sprites / images frames:
		//
		// [width, height]
		// [400, 200]
		//
		// [width, height, cellNumber, rowNumber]
		// [400, 200, 20, 10]
		//
		// [width, height, cellNumber, rowNumber, frameNumber]
		// [400, 200, 20, 10, 183]
		//
		// [width, height, spriteId, left, top]
		// [24, 24, "icons.png", 48, 0]
		//
		// [width, height, spriteId, left, top, cellNumber, rowNumber]
		// [100, 100, "explode.png", 420, 245, 5, 5]
		// Image is part of sprite image explode.png with offsets 420x245. It contains 25 images with each being 20x20.
		//
		// [width, height, spriteId, left, top, cellNumber, rowNumber, frameNumber]
		// [100, 100, "explode.png", 420, 245, 5, 5, 23]
		// Image is part of sprite image explode.png with offsets 420x245. It contains 23 images with each being 20x20.
		//
		// [width, height, spriteId, left, top, [[left,top,width,height], ...]]
		// [60, 40, "explode.png", 420, 245, [[0,0,20,20],[20,0,40,40],[0,20,20,20]]]
		// Image is part of sprite image with offsets 420x245. It contains 3 manually positioned images.
		//

		/**
		 * {Array} Returns the dimensions of the given image @id {String} with as `width`, `height`.
		 */
		getImageSize : function(id)
		{
			if (core.Env.isSet("debug")) {
				core.Assert.isType(id, "String", "Invalid asset ID!");
			}

			var entry = resolve(id);
			if (core.Env.isSet("debug") && !entry) {
				throw new Error("Could not figure out size of unknown image: " + id);
			}

			return entry.slice(0, 2);
		},

		/**
		 * {Integer} Returns the number of frames (for animations) for the given image @id {String}.
		 */
		getNumberOfFrames: function(id)
		{
			if (core.Env.isSet("debug")) {
				core.Assert.isType(id, "String", "Invalid asset ID!");
			}

			var entry = resolve(id);
			if (core.Env.isSet("debug") && !entry) {
				throw new Error("Could not figure out frame number of unknown image: " + id);
			}

			return getFrameNumber(entry);
		},

		/**
		 * {Map} Returns the image data for the given asset @id {String} with the keys:
		 * `src`, `left`, `top`, `width` and `height` to being used in a sprite compatible
		 * image rendering mechanism (e.g. CSS background image + position, Canvas `dragImage`, etc.)
		 */
		getImage : function(id)
		{
			var entry = resolve(id);
			if (core.Env.isSet("debug") && !entry) {
				throw new Error("Unknown image: " + id);
			}

			var width = entry[0];
			var height = entry[1];

			// We need more data than just the image sprite ID
			var spriteId = entry.length > 3 ? entry[2] : null;

			// Is part of image sprite?
			if (typeof spriteId == "string")
			{
				// Explicit root path
				if (spriteId.charAt(0) == "/") {
					spriteId = spriteId.slice(1);
				}

				// Local path (same folder as requested image)
				else if (spriteId.indexOf("/") == -1)
				{
					var pos = id.lastIndexOf("/");
					if (pos != -1) {
						spriteId = id.slice(0, pos+1) + spriteId;
					}
				}

				return {
					src : toUri(spriteId),
					left : entry[3],
					top : entry[4],
					width: width,
					height: height
				};
			}
			else
			{
				// Return compatible data format in cases where no sprite sheet is used
				return {
					src : root + (deployed ? id : entry[entry.length-1]),
					left : 0,
					top: 0,
					width: width,
					height: height
				};
			}
		},

		getFrame : function(id, frame)
		{
			var entry = resolve(id);
			if (core.Env.isSet("debug") && !entry) {
				throw new Error("Unknown image: " + id);
			}

			// Correct entry length for format detection
			var length = entry.length;

			if (deployed) {
				var src = root + id;
			} else {
				var src = root + entry[--length];
			}

			var left=0, top=0, width=entry[0], height=entry[1], offsetLeft=0, offsetTop=0;

			// Detect whether a frame is available
			if (length > 3 || length < 9)
			{

				//console.debug("FRAME-REL-LENGTH: ", length)
				var number = getFrameNumber(entry);

				if (frame >= number) {
					throw new Error("Invalid frame number " + frame + " for asset " + id + "!");
				}

				// Manual frames
				if (length == 6)
				{

				}

				// Automatic frames
				else
				{
					// Correctly work when using sprite images
					var cols = length > 6 ? entry[5] : entry[2];
					var rows = length > 6 ? entry[6] : entry[3];

					// Correct image dimensions
					width /= cols;
					height /= rows;

					// Calculate position inside sprite image
					left = (frame % cols) * width;
					top = Math.floor(frame / cols) * height;
				}
			}
			else if (frame != 0 && core.Env.isSet("debug"))
			{
				throw new Error("Invalid frame number " + frame + " for asset " + id + "!");
			}

			return {
				src : src,
				left : left,
				top : top,
				width : width,
				height : height,
				offsetLeft : offsetLeft,
				offsetTop : offsetTop
			};

		}

	});

	core.io.Asset.reset();
	core.io.Asset.add(core.Env.getValue("assets"));

})(this);

<commit_msg>Minor optimizations<commit_after>/*
==================================================================================================
  Core - JavaScript Foundation
  Copyright 2010-2012 Zynga Inc.
==================================================================================================
*/

(function(global)
{
	// Internal data storage
	var deployed, root, assets;

	/**
	 * {Array} Resolves the given @id {String} into the stored entry of the asset data base.
	 */
	var resolve = function(id)
	{
		if (id.constructor == Object) {
			return id;
		}

		if (core.Env.isSet("debug")) {
			core.Assert.isType(id, "String");
		}

		var splits = id.split("/");
		var current = assets;
		for (var i=0, l=splits.length; current&&i<l; i++) {
			current = current[splits[i]];
		}

		if (current == null) {
			console.warn("Could not resolve URI: " + id);
		}

		return current;
	};

	/**
	 * {Map} Collects asset with the given asset @prefix {String} from the given @section {Map}.
	 * Optionally works recursively by enabling @recursive {Boolean?false} or only
	 * returns the data by enabling @data {Boolean?false}. Last parameter @all {Map?}
	 * is used to fill an existing object instead of a new one.
	 */
	var collect = function(prefix, cut, section, recursive, data, all)
	{
		if (!all) {
			all = {};
		}

		if (section)
		{
			for (var filename in section)
			{
				var entry = section[filename];
				var id = prefix + "/" + filename;

				if (entry.constructor == Object)
				{
					if (recursive) {
						collect(id, cut, entry, recursive, data, all);
					}
				}
				else
				{
					var localId = !cut ? id : id.slice(cut);

					if (data) {
						all[localId] = entry;
					} else if (deployed) {
						all[localId] = root + id;
					} else {
						all[localId] = root + entry[0];
					}
				}
			}
		}

		return all;
	};

	/**
	 * {String} Converts the given asset @id {String} to a full qualified URI.
	 * The method throws an error whenever an asset ID is unknown.
	 */
	var toUri = function(id)
	{
		if (core.Env.isSet("debug")) {
			core.Assert.isType(id, "String");
		}

		if (id.charAt("0") == "/" || id.startsWith("http:") || id.startsWith("https:")) {
			return id;
		}

		if (deployed)
		{
			return root + id;
		}
		else
		{
			var entry = resolve(id);
			if (core.Env.isSet("debug") && !entry) {
				throw new Error("Can't resolve URL for asset: " + id);
			}

			return root + entry[entry.length-1];
		}
	};

	/**
	 * Merged two data maps @src {Map} and @dst {Map} recursively by using as much of
	 * the original data as possible (no copying). Will never override existing values!
	 */
	var mergeData = function(src, dst)
	{
		for (var key in src)
		{
			var srcValue = src[key];
			var dstValue = dst[key];

			// Just use value from source (works for folders and files)
			if (dstValue == null) {
				dst[key] = srcValue;
			}

			// Deep merge
			else if (srcValue.constructor === Object && dstValue.construtor === Object) {
				mergeData(srcValue, dstValue);
			}
		}
	};

	var getFrameNumber = function(entry)
	{
		// Correct entry length for format detection
		var length = entry.length;
		if (!deployed) {
			length--;
		}

		switch(length)
		{
			case 4:
				// auto calculated frame size
				return entry[2] * entry[3];

			case 5:
				// manually defined frame size
				return entry[4];

			case 6:
				// manually defined frames
				return entry[5].length;

			case 7:
				// auto calculated frame size (image sprite)
				return entry[5] * entry[6];

			case 8:
				// manually defined frame size (image sprite)
				return entry[7];
		}

		return 1;
	};

	/**
	 * Contains information about images (size, format, clipping, ...) and
	 * other assets like CSS files, local data, ...
	 *
	 * #require(ext.sugar.Array)
	 * #require(ext.sugar.Object)
	 * #require(ext.sugar.String)
	 */
	core.Module("core.io.Asset",
	{
		reset : function()
		{
			deployed = root = null;
			assets = {};
		},

		isDeployed: function(data) {
			return deployed;
		},

		add : function(data)
		{
			// Validate input data
			if (core.Env.isSet("debug"))
			{
				core.Assert.isType(data, "Map");
				core.Assert.isType(data.assets, "Map");
				core.Assert.isType(data.root, "String");
				core.Assert.isType(data.deployed, "Boolean");
			}

			// Initial data
			if (root == null)
			{
				assets = data.assets;
				deployed = data.deployed;
				root = data.root;
			}

			// Inject data
			else
			{
				if (core.Env.isSet("debug"))
				{
					if (data.deployed != deployed) {
						throw new Error("Cannot handle deployed and undeployed assets into one data set!");
					}

					if (data.root != root) {
						throw new Error("Cannot handle two different roots in on data set!");
					}
				}

				mergeData(data.assets, assets);
			}

		},

		toUri : toUri,
		resolve: resolve,

		/**
		 * {Boolean} Whether the registry has information about the given asset @id {String}.
		 */
		has : function(id)
		{
			if (core.Env.isSet("debug")) {
				core.Assert.isType(id, "String");
			}

			return !!resolve(id);
		},

		/**
		 * Loads the given @section {String}. Optionally @recursive {Boolean?false} it traverses
		 * the whole tree starting at @section. The optional @callback {Function?} is executed
		 * in the given @context {Object?} when all files are loaded. It is called with one
		 * parameter which contains are `Map` of all data (relative asset ID to loaded item).
		 * Optionally caching can be disabled by attaching a random `GET` parameter.
		 */
		loadSection: function(section, recursive, callback, context, nocache)
		{
			if (core.Env.isSet("debug"))
			{
				core.Assert.isType(section, "String");

				if (section == "") {
					throw new Error("Invalid section: " + section);
				}

				if (section.endsWith("/")) {
					throw new Error("Sections must not end with a slash!")
				}

				if (recursive != null) {
					core.Assert.isType(recursive, "Boolean");
				}

				if (callback != null) {
					core.Assert.isType(callback, "Function");
				}

				if (context != null) {
					core.Assert.isType(context, "Object");
				}

				if (nocache != null) {
					core.Assert.isType(nocache, "Boolean");
				}
			}

			// Collect assets
			var all = collect(section, section.length + 1, resolve(section), recursive);
			var uris = Object.values(all);

			// Build back tables to translate uris back to local IDs
			var urisToIds = uris.zip(Object.keys(all));

			var helper = callback ? function(data) {
				callback.call(context||global, Object.translate(data, urisToIds));
			} : callback;

			core.io.Queue.load(uris, helper, this, nocache);
		},

		/**
		 * Loads the given assets by their ID and executes @callback {Function?}
		 * in the given @context {Object?global}. Optionally caching can be disabled
		 * by attaching a random `GET` parameter.
		 */
		load: function(ids, callback, context, nocache)
		{
			if (core.Env.isSet("debug"))
			{
				core.Assert.isType(ids, "Array");

				if (callback != null) {
					core.Assert.isType(callback, "Function");
				}

				if (context != null) {
					core.Assert.isType(context, "Object");
				}

				if (nocache != null) {
					core.Assert.isType(nocache, "Boolean");
				}
			}

			var uris = ids.map(toUri);
			var urisToIds = uris.zip(ids);

			var helper = callback ? function(data) {
				callback.call(context||global, Object.translate(data, urisToIds));
			} : callback;

			core.io.Queue.load(uris, helper, this, nocache);
		},

		//
		// Data formats for image sprites / images frames:
		//
		// [width, height]
		// [400, 200]
		//
		// [width, height, cellNumber, rowNumber]
		// [400, 200, 20, 10]
		//
		// [width, height, cellNumber, rowNumber, frameNumber]
		// [400, 200, 20, 10, 183]
		//
		// [width, height, spriteId, left, top]
		// [24, 24, "icons.png", 48, 0]
		//
		// [width, height, spriteId, left, top, cellNumber, rowNumber]
		// [100, 100, "explode.png", 420, 245, 5, 5]
		// Image is part of sprite image explode.png with offsets 420x245. It contains 25 images with each being 20x20.
		//
		// [width, height, spriteId, left, top, cellNumber, rowNumber, frameNumber]
		// [100, 100, "explode.png", 420, 245, 5, 5, 23]
		// Image is part of sprite image explode.png with offsets 420x245. It contains 23 images with each being 20x20.
		//
		// [width, height, spriteId, left, top, [[left,top,width,height], ...]]
		// [60, 40, "explode.png", 420, 245, [[0,0,20,20],[20,0,40,40],[0,20,20,20]]]
		// Image is part of sprite image with offsets 420x245. It contains 3 manually positioned images.
		//

		/**
		 * {Array} Returns the dimensions of the given image @id {String} with as `width`, `height`.
		 */
		getImageSize : function(id)
		{
			if (core.Env.isSet("debug")) {
				core.Assert.isType(id, "String", "Invalid asset ID!");
			}

			var entry = resolve(id);
			if (core.Env.isSet("debug") && !entry) {
				throw new Error("Could not figure out size of unknown image: " + id);
			}

			return entry.slice(0, 2);
		},

		/**
		 * {Integer} Returns the number of frames (for animations) for the given image @id {String}.
		 */
		getNumberOfFrames: function(id)
		{
			if (core.Env.isSet("debug")) {
				core.Assert.isType(id, "String", "Invalid asset ID!");
			}

			var entry = resolve(id);
			if (core.Env.isSet("debug") && !entry) {
				throw new Error("Could not figure out frame number of unknown image: " + id);
			}

			return getFrameNumber(entry);
		},

		/**
		 * {Map} Returns the image data for the given asset @id {String} with the keys:
		 * `src`, `left`, `top`, `width` and `height` to being used in a sprite compatible
		 * image rendering mechanism (e.g. CSS background image + position, Canvas `dragImage`, etc.)
		 */
		getImage : function(id)
		{
			var entry = resolve(id);
			if (!entry && core.Env.isSet("debug")) {
				throw new Error("Unknown image: " + id);
			}

			var width = entry[0];
			var height = entry[1];

			// We need more data than just the image sprite ID
			var spriteId = entry.length > 3 ? entry[2] : null;

			// Is part of image sprite?
			if (typeof spriteId == "string")
			{
				// Explicit root path
				if (spriteId.charAt(0) == "/") {
					spriteId = spriteId.slice(1);
				}

				// Local path (same folder as requested image)
				else if (spriteId.indexOf("/") == -1)
				{
					var pos = id.lastIndexOf("/");
					if (pos != -1) {
						spriteId = id.slice(0, pos+1) + spriteId;
					}
				}

				return {
					src : toUri(spriteId),
					left : entry[3],
					top : entry[4],
					width: width,
					height: height
				};
			}
			else
			{
				// Return compatible data format in cases where no sprite sheet is used
				return {
					src : root + (deployed ? id : entry[entry.length-1]),
					left : 0,
					top: 0,
					width: width,
					height: height
				};
			}
		},

		getFrame : function(id, frame)
		{
			var entry = resolve(id);
			if (!entry && core.Env.isSet("debug")) {
				throw new Error("Unknown image: " + id);
			}

			// Correct entry length for format detection
			var length = entry.length;

			if (deployed) {
				var src = root + id;
			} else {
				var src = root + entry[--length];
			}

			var left=0, top=0, width=entry[0], height=entry[1], offsetLeft=0, offsetTop=0;

			// Detect whether a frame is available
			if (length > 3 || length < 9)
			{

				//console.debug("FRAME-REL-LENGTH: ", length)
				var number = getFrameNumber(entry);

				if (frame >= number && core.Env.isSet("debug")) {
					throw new Error("Invalid frame number " + frame + " for asset " + id + "!");
				}

				// Manual frames
				if (length == 6)
				{

				}

				// Automatic frames
				else
				{
					// Correctly work when using sprite images
					var cols = length > 6 ? entry[5] : entry[2];
					var rows = length > 6 ? entry[6] : entry[3];

					// Correct image dimensions
					width /= cols;
					height /= rows;

					// Calculate position inside sprite image
					left = (frame % cols) * width;
					top = Math.floor(frame / cols) * height;
				}
			}
			else if (frame != 0 && core.Env.isSet("debug"))
			{
				throw new Error("Invalid frame number " + frame + " for asset " + id + "!");
			}

			return {
				src : src,
				left : left,
				top : top,
				width : width,
				height : height,
				offsetLeft : offsetLeft,
				offsetTop : offsetTop
			};

		}

	});

	core.io.Asset.reset();
	core.io.Asset.add(core.Env.getValue("assets"));

})(this);

<|endoftext|>


<commit_before>/**
 * Copyright 2015 CANAL+ Group
 *
 * 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.
 */

var _ = require("canal-js-utils/misc");
var log = require("canal-js-utils/log");
var assert = require("canal-js-utils/assert");
var { parseBaseURL } = require("canal-js-utils/url");
var { isCodecSupported } = require("./compat");

var representationBaseType = [
  "profiles",
  "width",
  "height",
  "frameRate",
  "audioSamplingRate",
  "mimeType",
  "segmentProfiles",
  "codecs",
  "maximumSAPPeriod",
  "maxPlayoutRate",
  "codingDependency",
  "index",
];

var SUPPORTED_ADAPTATIONS_TYPE = ["audio", "video", "text"];
var DEFAULT_PRESENTATION_DELAY = 15;

function parseType(mimeType) {
  return mimeType.split("/")[0];
}

function normalizeManifest(location, manifest, subtitles) {
  assert(manifest.transportType);

  manifest.id = manifest.id || _.uniqueId();
  manifest.type = manifest.type || "static";

  var locations = manifest.locations;
  if (!locations || !locations.length) {
    manifest.locations = [location];
  }

  manifest.isLive = manifest.type == "dynamic";

  // TODO(pierre): support multi-locations/cdns
  var urlBase = {
    rootURL: parseBaseURL(manifest.locations[0]),
    baseURL: manifest.baseURL,
    isLive: manifest.isLive,
  };

  if (subtitles) {
    subtitles = normalizeSubtitles(subtitles);
  }

  var periods = _.map(manifest.periods, period => normalizePeriod(period, urlBase, subtitles));

  // TODO(pierre): support multiple periods
  _.extend(manifest, periods[0]);
  manifest.periods = null;

  if (!manifest.duration)
    manifest.duration = Infinity;

  if (manifest.isLive) {
    manifest.suggestedPresentationDelay = manifest.suggestedPresentationDelay || DEFAULT_PRESENTATION_DELAY;
    manifest.availabilityStartTime = manifest.availabilityStartTime || 0;
  }

  return manifest;
}

function normalizePeriod(period, inherit, subtitles) {
  period.id = period.id || _.uniqueId();

  var adaptations = period.adaptations;
  adaptations = adaptations.concat(subtitles || []);
  adaptations = _.map(adaptations, ada => normalizeAdaptation(ada, inherit));
  adaptations = _.filter(adaptations, (adaptation) => {
    if (SUPPORTED_ADAPTATIONS_TYPE.indexOf(adaptation.type) < 0) {
      log.warn("not supported adaptation type", adaptation.type);
      return false;
    } else {
      return true;
    }
  });

  assert(adaptations.length > 0);

  period.adaptations = _.groupBy(adaptations, "type");
  return period;
}

function normalizeAdaptation(adaptation, inherit) {
  adaptation.id = adaptation.id || _.uniqueId();
  _.defaults(adaptation, inherit);

  var inheritedFromAdaptation = _.pick(adaptation, representationBaseType);
  var representations = _.map(adaptation.representations,
    rep => normalizeRepresentation(rep, inheritedFromAdaptation))
    .sort((a, b) => a.bitrate - b.bitrate);

  var { type, mimeType } = adaptation;
  if (!mimeType)
    mimeType = representations[0].mimeType;

  assert(mimeType);

  adaptation.mimeType = mimeType;

  if (!type)
    type = adaptation.type = parseType(mimeType);

  if (type == "video" || type == "audio") {
    representations = _.filter(representations, rep => isCodecSupported(getCodec(rep)));
  }

  assert(representations.length > 0, "manifest: no compatible representation for this adaptation");
  adaptation.representations = representations;
  adaptation.bitrates = _.pluck(representations, "bitrate");
  return adaptation;
}

function normalizeRepresentation(representation, inherit) {
  representation.id = representation.id || _.uniqueId();
  _.defaults(representation, inherit);

  var index = representation.index;
  assert(index);

  if (!index.timescale) {
    index.timescale = 1;
  }

  if (!representation.bitrate) {
    representation.bitrate = 1;
  }

  // Fix issue in some packagers, like GPAC, generating a non
  // compliant mimetype with RFC 6381. Other closed-source packagers
  // maybe impacted.
  if (representation.codecs == "mp4a.40.02") {
    representation.codecs = "mp4a.40.2";
  }

  return representation;
}

function normalizeSubtitles(subtitles) {
  if (!_.isArray(subtitles))
    subtitles = [subtitles];

  return _.flatten(subtitles, ({ mimeType, url, language, languages }) => {
    if (language) {
      languages = [language];
    }

    return _.map(languages, lang => ({
      id: _.uniqueId(),
      type: "text",
      lang,
      mimeType,
      rootURL: url,
      baseURL: "",
      representations: [{
        id: _.uniqueId(),
        mimeType,
        index: {
          indexType: "template",
          duration: Number.MAX_VALUE,
          timescale: 1,
          startNumber: 0,
        }
      }]
    }));
  });
}

function mergeManifestsIndex(oldManifest, newManifest) {
  var oldAdaptations = oldManifest.adaptations;
  var newAdaptations = newManifest.adaptations;
  for (var type in oldAdaptations) {
    var oldAdas = oldAdaptations[type];
    var newAdas = newAdaptations[type];
    _.each(oldAdas, (a, i) => {
      _.simpleMerge(a.index, newAdas[i].index);
    });
  }
  return oldManifest;
}

function mutateManifestLiveGap(manifest, addedTime=1) {
  if (manifest.isLive) {
    manifest.presentationLiveGap += addedTime;
  }
}

function getCodec(representation) {
  var { codecs, mimeType } = representation;
  return `${mimeType};codecs="${codecs}"`;
}

function getAdaptations(manifest) {
  var adaptationsByType = manifest.adaptations;

  var adaptationsList = [];
  _.each(Object.keys(adaptationsByType), (type) => {
    var adaptations = adaptationsByType[type];
    adaptationsList.push({
      type: type,
      adaptations: adaptations,
      codec: getCodec(adaptations[0].representations[0])
    });
  });

  return adaptationsList;
}

function getAvailableLanguages(manifest) {
  return _.pluck(manifest.adaptations.audio, "lang");
}

function getAvailableSubtitles(manifest) {
  return _.pluck(manifest.adaptations.text, "lang");
}

module.exports = {
  normalizeManifest,
  mergeManifestsIndex,
  mutateManifestLiveGap,
  getCodec,
  getAdaptations,
  getAvailableSubtitles,
  getAvailableLanguages,
};
<commit_msg>use _.keys instead of Object.keys<commit_after>/**
 * Copyright 2015 CANAL+ Group
 *
 * 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.
 */

var _ = require("canal-js-utils/misc");
var log = require("canal-js-utils/log");
var assert = require("canal-js-utils/assert");
var { parseBaseURL } = require("canal-js-utils/url");
var { isCodecSupported } = require("./compat");

var representationBaseType = [
  "profiles",
  "width",
  "height",
  "frameRate",
  "audioSamplingRate",
  "mimeType",
  "segmentProfiles",
  "codecs",
  "maximumSAPPeriod",
  "maxPlayoutRate",
  "codingDependency",
  "index",
];

var SUPPORTED_ADAPTATIONS_TYPE = ["audio", "video", "text"];
var DEFAULT_PRESENTATION_DELAY = 15;

function parseType(mimeType) {
  return mimeType.split("/")[0];
}

function normalizeManifest(location, manifest, subtitles) {
  assert(manifest.transportType);

  manifest.id = manifest.id || _.uniqueId();
  manifest.type = manifest.type || "static";

  var locations = manifest.locations;
  if (!locations || !locations.length) {
    manifest.locations = [location];
  }

  manifest.isLive = manifest.type == "dynamic";

  // TODO(pierre): support multi-locations/cdns
  var urlBase = {
    rootURL: parseBaseURL(manifest.locations[0]),
    baseURL: manifest.baseURL,
    isLive: manifest.isLive,
  };

  if (subtitles) {
    subtitles = normalizeSubtitles(subtitles);
  }

  var periods = _.map(manifest.periods, period => normalizePeriod(period, urlBase, subtitles));

  // TODO(pierre): support multiple periods
  _.extend(manifest, periods[0]);
  manifest.periods = null;

  if (!manifest.duration)
    manifest.duration = Infinity;

  if (manifest.isLive) {
    manifest.suggestedPresentationDelay = manifest.suggestedPresentationDelay || DEFAULT_PRESENTATION_DELAY;
    manifest.availabilityStartTime = manifest.availabilityStartTime || 0;
  }

  return manifest;
}

function normalizePeriod(period, inherit, subtitles) {
  period.id = period.id || _.uniqueId();

  var adaptations = period.adaptations;
  adaptations = adaptations.concat(subtitles || []);
  adaptations = _.map(adaptations, ada => normalizeAdaptation(ada, inherit));
  adaptations = _.filter(adaptations, (adaptation) => {
    if (SUPPORTED_ADAPTATIONS_TYPE.indexOf(adaptation.type) < 0) {
      log.warn("not supported adaptation type", adaptation.type);
      return false;
    } else {
      return true;
    }
  });

  assert(adaptations.length > 0);

  period.adaptations = _.groupBy(adaptations, "type");
  return period;
}

function normalizeAdaptation(adaptation, inherit) {
  adaptation.id = adaptation.id || _.uniqueId();
  _.defaults(adaptation, inherit);

  var inheritedFromAdaptation = _.pick(adaptation, representationBaseType);
  var representations = _.map(adaptation.representations,
    rep => normalizeRepresentation(rep, inheritedFromAdaptation))
    .sort((a, b) => a.bitrate - b.bitrate);

  var { type, mimeType } = adaptation;
  if (!mimeType)
    mimeType = representations[0].mimeType;

  assert(mimeType);

  adaptation.mimeType = mimeType;

  if (!type)
    type = adaptation.type = parseType(mimeType);

  if (type == "video" || type == "audio") {
    representations = _.filter(representations, rep => isCodecSupported(getCodec(rep)));
  }

  assert(representations.length > 0, "manifest: no compatible representation for this adaptation");
  adaptation.representations = representations;
  adaptation.bitrates = _.pluck(representations, "bitrate");
  return adaptation;
}

function normalizeRepresentation(representation, inherit) {
  representation.id = representation.id || _.uniqueId();
  _.defaults(representation, inherit);

  var index = representation.index;
  assert(index);

  if (!index.timescale) {
    index.timescale = 1;
  }

  if (!representation.bitrate) {
    representation.bitrate = 1;
  }

  // Fix issue in some packagers, like GPAC, generating a non
  // compliant mimetype with RFC 6381. Other closed-source packagers
  // maybe impacted.
  if (representation.codecs == "mp4a.40.02") {
    representation.codecs = "mp4a.40.2";
  }

  return representation;
}

function normalizeSubtitles(subtitles) {
  if (!_.isArray(subtitles))
    subtitles = [subtitles];

  return _.flatten(subtitles, ({ mimeType, url, language, languages }) => {
    if (language) {
      languages = [language];
    }

    return _.map(languages, lang => ({
      id: _.uniqueId(),
      type: "text",
      lang,
      mimeType,
      rootURL: url,
      baseURL: "",
      representations: [{
        id: _.uniqueId(),
        mimeType,
        index: {
          indexType: "template",
          duration: Number.MAX_VALUE,
          timescale: 1,
          startNumber: 0,
        }
      }]
    }));
  });
}

function mergeManifestsIndex(oldManifest, newManifest) {
  var oldAdaptations = oldManifest.adaptations;
  var newAdaptations = newManifest.adaptations;
  for (var type in oldAdaptations) {
    var oldAdas = oldAdaptations[type];
    var newAdas = newAdaptations[type];
    _.each(oldAdas, (a, i) => {
      _.simpleMerge(a.index, newAdas[i].index);
    });
  }
  return oldManifest;
}

function mutateManifestLiveGap(manifest, addedTime=1) {
  if (manifest.isLive) {
    manifest.presentationLiveGap += addedTime;
  }
}

function getCodec(representation) {
  var { codecs, mimeType } = representation;
  return `${mimeType};codecs="${codecs}"`;
}

function getAdaptations(manifest) {
  var adaptationsByType = manifest.adaptations;

  var adaptationsList = [];
  _.each(_.keys(adaptationsByType), (type) => {
    var adaptations = adaptationsByType[type];
    adaptationsList.push({
      type: type,
      adaptations: adaptations,
      codec: getCodec(adaptations[0].representations[0])
    });
  });

  return adaptationsList;
}

function getAvailableLanguages(manifest) {
  return _.pluck(manifest.adaptations.audio, "lang");
}

function getAvailableSubtitles(manifest) {
  return _.pluck(manifest.adaptations.text, "lang");
}

module.exports = {
  normalizeManifest,
  mergeManifestsIndex,
  mutateManifestLiveGap,
  getCodec,
  getAdaptations,
  getAvailableSubtitles,
  getAvailableLanguages,
};
<|endoftext|>


<commit_before>/**
 * from detect-node
 * https://github.com/iliakan/detect-node
 *
 * @property {boolean} IS_NODE - whether running in nodejs but not on electron
 * @global
 * @name IS_NODE
 */
export const IS_NODE = Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]' && !process.versions['electron'];
<commit_msg>whether running in nodejs but neither on electron nor node-webkit<commit_after>/**
 * from detect-node
 * https://github.com/iliakan/detect-node
 *
 * @property {boolean} IS_NODE - whether running in nodejs but not on electron,node-webkit
 * @global
 * @name IS_NODE
 */
export const IS_NODE = Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]' && !process.versions['electron'] && !process.versions['nw'] && !process.versions['node-webkit'];
<|endoftext|>