asset.js

const extensions = require('./extensions')
const { Source } = require('./source')
const through = require('through2')
const getUri = require('get-uri')
const debug = require('debug')('little-media-box:asset')
const ready = require('nanoresource-ready')
const path = require('path')
const pump = require('pump')
const uuid = require('uuid/v4')
const url = require('url')
const fs = require('fs')

/**
 * The `Asset` class represents a container for an external asset
 * that has semantic associations with other objects.
 * @public
 * @class
 * @extends nanoresource
 */
class Asset extends Source {

  /**
   * `Asset` class constructor.
   * @param {String} uri
   * @param {?(Object)} opts
   * @param {?(String)} opts.id
   * @param {?(String)} opts.cwd
   * @param {?(String)} opts.type
   * @param {?(Array<String>)} opts.associations
   */
  constructor(uri, opts) {
    super()

    if (!opts || 'object' !== typeof opts) {
      opts = {}
    }

    this.id = opts.id || uuid()
    this.uri = uri
    this.cwd = opts.cwd || process.cwd()
    this.type = opts.type || extensions.typeof(path.extname(this.uri))
    this.byteLength = 0
    this.associations = Array.from(opts.associations || [])
  }

  /**
   * Implements the abstract `_open()` method for `nanoresource`
   * Opens the asset source stream and initializes internal state.
   * @protected
   * @param {Function} callback
   */
  _open(callback) {
    const uri = url.parse(this.uri)
    if (/https?:/.test(uri.protocol)) {
      head(this.uri, (err, res) => {
        if (err) { return callback(err) }
        this.byteLength = parseInt(res.headers['content-length'])
        callback(null)
      })
    } else {
      const pathname = path.resolve(this.cwd, uri.path)
      fs.stat(pathname, (err, stats) => {
        if (err) { return callback(err) }
        this.uri = `file://${pathname}`
        this.byteLength = stats.size
        callback(null)
      })
    }
  }

  /**
   * Wait for asset to be ready (opened) calling `callback()`
   * when it is.
   * @param {Function}
   */
  ready(callback) {
    ready(this, callback)
  }

  /**
   * Creates a read stream for the asset URI.
   * @param {?(Object)} opts
   * @return {Stream}
   */
  createReadStream(opts) {
    if (!opts || 'object' !== typeof opts) {
      opts = {}
    }

    const readStream = through()
    const { uri } = this

    getUri(uri, onstream)

    return readStream

    function onstream(err, sourceStream) {
      if (err) { return readStream.emit('error', err) }
      pump(sourceStream, readStream, onpump)
    }

    function onpump(err) {
      if (err) { return readStream.emit('error', err) }
    }
  }
}

/**
 * Module exports.
 */
module.exports = {
  Asset
}