Home Reference Source Repository

src/component/cache/abstract-driver.js

'use strict';

const fs = require('fs');
const jag = require('jag');
const fse = require('fs-extra');
const path = require('path');
const pify = require('pify');
const events = require('./events');
const EventEmitter = require('events');

/**
 * Abstract cache driver
 */
class AbstractDriver extends EventEmitter {
  /**
   * @param {String} cacheDir
   */
  constructor(cacheDir) {
    super();
    
    this._cacheDir = cacheDir;
    this._progressType = null;
  }
  
  /**
   * @returns {string}
   */
  get cacheDir() {
    return this._cacheDir;
  }
  
  /**
   * @returns {Promise}
   */
  upload() {
    return this._switchProgressType('upload')
      ._removePackageFile()
      .then(() => this._pack())
      .then(() => this._upload())
      .then(() => this._resetOperation());
  }
  
  /**
   * @returns {Promise}
   */
  download() {
    return this._switchProgressType('download')
      ._removePackageFile()
      .then(() => this._download())
      .then(() => this._unpack())
      .then(() => this._resetOperation());
  }
  
  /**
   * @param {string} type
   *
   * @returns {AbstractDriver}
   *
   * @private
   */
  _switchProgressType(type) {
    this._progressType = type;
    
    return this;
  }
  
  /**
   * @param {number} total
   * @param {number} amount
   *
   * @returns {AbstractDriver}
   *
   * @private
   */
  _progress(total, amount) {
    const payload = {
      amount, total, remaining: total - amount,
    };
    
    this.emit(events.cache.progress, payload);
    this.emit(events.cache[this._progressType].progress, payload);
    
    return this;
  }
  
  /**
   * @returns {Promise}
   * 
   * @private
   */
  _resetOperation() {
    this._progressType = null;
    
    return this._removePackageFile();
  }
  
  /**
   * @returns {Promise}
   * 
   * @private
   */
  _removePackageFile() {
    return fse.remove(this._packagePath);
  }
  
  /**
   * @returns {string}
   *
   * @private
   */
  get _packagePath() {
    return path.join(
      path.dirname(this.cacheDir),
      (path.basename(this.cacheDir) || AbstractDriver.DEFAULT_NS) + '.tar.gz'
    );
  }
  
  /**
   * @returns {Promise}
   * @private
   */
  _pack() {
    return fse.pathExists(this.cacheDir).then(result => {
      if (!result) {
        return Promise.resolve();
      }

      return pify(jag.pack)(this.cacheDir, this._packagePath).then(() => {
        // @todo remove when fixed
        // @see https://github.com/coderaiser/node-jag/blob/master/lib/jag.js#L53
        const tmpPackagePath = path.join(
          path.dirname(this._packagePath),
          path.basename(this._packagePath, '.tar.gz')
        ) + '.tar.tar.gz';

        return fse.move(tmpPackagePath, this._packagePath, { overwrite: true });
      });
    });
  }
  
  /**
   * @returns {Promise}
   * @private
   */
  _unpack() {
    return fse.pathExists(this._packagePath).then(result => {
      if (!result) {
        return Promise.resolve();
      }

      return this._packageSize.then(packageSize => {
        if (packageSize <= 0) {
          return Promise.resolve();
        }

        return fse.ensureDir(this.cacheDir).then(() => pify(jag.unpack)(this._packagePath, this.cacheDir));
      });
    });
  }

  /**
   * @returns {Promise}
   *
   * @private
   */
  get _packageSize() {
    return pify(fs.stat)(this._packagePath)
      .then(stat => Promise.resolve(stat.size));
  }
  
  /**
   * @returns {Promise}
   *
   * @private
   */
  _upload() {
    return Promise.reject(new Error(
      `${ this.constructor.name }._upload() not implemented!`
    ));
  }
  
  /**
   * @throws {Error}
   */
  get name() {
    throw new Error(
      `${ this.constructor.name }.name not implemented!`
    );
  }

  /**
   * @returns {Promise}
   *
   * @private
   */
  _download() {
    return Promise.reject(new Error(
      `${ this.constructor.name }._download() not implemented!`
    ));
  }
  
  /**
   * @returns {string}
   */
  static get DEFAULT_NS() {
    return 'recink_package';
  }
}

module.exports = AbstractDriver;