<commit_before>import crypto from 'crypto';
import path from 'path';

import AppCache from 'parse-server/lib/cache';

const DeployInfoBasePath = "_deployInfo";
const DeployInfoCollectionName = "deployInfo";
const FilesCollectionName = "deployFile";

function computeChecksum(content){
    var algo = 'md5',
        md5sum = crypto.createHash(algo);
    md5sum.update(content, 'binary');
    return md5sum.digest('hex');
}

class ParseCliController {
  constructor(vendorAdapter: any){
    this.vendorAdapter = vendorAdapter;
  }

  getCliLatestVersion(){
    return this.vendorAdapter.getCliLatestVersion();
  }

  isSupported(mode, other, version){
    return new Promise((resolve, reject) => {
      // local commands must be defined
      var localCommands = [
        "default",
        "generate",
        "-h",
        "--help",
        "help",
        "migrate",
        "update",
        "version",
      ],
      commands = [
        "add",
        "configure accountkey",
        "deploy",
        "develop",
        "download",
        "functions",
        "jssdk",
        "list",
        "logs",
        "new",
        "releases",
        "rollback",
        "triggers",
      ],
      nokCommands = [
        "symbols",
      ],
      tokens = {};
      localCommands.forEach(value => {
        tokens[value] = true;
      });
      commands.forEach(value => {
        tokens[value] = true;
      });

      if (mode == 'parse') {
        var parts = other.split(" ");
        for (var i = 1; i <= parts.length; i++) {
          var token = parts.slice(0, i).join(" ");
          if (tokens[token]) {
            resolve({});
            return;
          }
        }
      }
      reject();
    });
  }

  getAccountKey(email, password){
    return this.vendorAdapter.getAccountKey(email, password);
  }
  getEmail(accountKey){
    return this.vendorAdapter.getEmail(accountKey);
  }

  getJsVersions(appId){
    return this.vendorAdapter.getJsVersions(appId);
  }

  getApps(accountKey) {
    return this.vendorAdapter.getApps(accountKey);
  }

  getApp(accountKey, appId) {
    return this.vendorAdapter.getApp(accountKey, appId);
  }

  createApp(accountKey, appName) {
    return this.vendorAdapter.createApp(accountKey, appName);
  }

  getFile(appId, folder, filename, version, checksum){
    let config = AppCache.get(appId),
        query = { folder, filename, version, checksum };
    return config.databaseController.find(
      this.getCollectionName(appId, FilesCollectionName),
      query)
    .then(objects => {
      if (objects.length == 0) {
        throw new Error("Object " + JSON.stringify(query) + " not found.");
      }
      var object = objects[0];
      return config.filesController.getFileData(config, object.name)
      .then(data => {
        // Data is base64 encoded
        if (object.encoding == 'base64') {
          data = new Buffer(data.toString(), 'base64');
        }
        return data;
      });
    });
  }

  uploadFile(appId, folder, filename, content){

    let config = AppCache.get(appId),
        key = path.join(folder, filename),
        binaryContent = new Buffer(content, 'base64').toString('binary'),
        checksum = computeChecksum(binaryContent),
        filesCollectionName = this.getCollectionName(appId, FilesCollectionName);

    return config.databaseController.find(filesCollectionName, {
        folder: folder,
        filename: filename
      }, {
        sort: {createdAt: 1}
      })
    .then(objects => {
      var nextVersion;

      if (objects.length == 0) {
        nextVersion = 1;
      }
      else {
        var object = objects[0],
            currentVersion = object.version;
        nextVersion = parseInt(currentVersion) + 1;
      }
      return nextVersion.toString();
    })
    .then(version => config.filesController.createFile(
        config,
        path.join(this.getBasePath(appId, DeployInfoBasePath), key),
        content)
      .then(obj => config.databaseController.create(filesCollectionName, {
        version: version,
        checksum: checksum,
        folder: folder,
        filename: filename,
        name: obj.name,
        url: obj.url,
        encoding: 'base64',
      }))
      .then(() => {
        return {
          checksum: checksum,
          version: version
        };
      })
    );
  }

  _findDeployInfo(appId, query, sort) {
    let config = AppCache.get(appId);
    let options = {
      sort: sort,
      limit: 1
    }
    return config.databaseController.find(
      this.getCollectionName(appId, DeployInfoCollectionName),
      query,
      options)
    .then(results => {
      return results.map(deployInfo => {
        deployInfo.files = deployInfo.files.filter( file => {
          return file.filename.indexOf('node_modules') !== 0;
        });
        return this._unpatchDeployInfo(deployInfo);
      });
    })
    .then(results => {
      return results.length !== 0 ? results[0] : null;
    });
  }

  getDeployInfo(appId){
    return this._findDeployInfo(appId, {}, {deployedAt: -1, createdAt: -1});
  }

  _setDeployInfo(appId, deployInfo){
    let config = AppCache.get(appId);
    let className = this.getCollectionName(appId, DeployInfoCollectionName);
    let query = {releaseId: deployInfo.releaseId};
    let now = new Date().toISOString();
    if (deployInfo.createdAt) {
      return config.databaseController.update(
        className, query, {deployedAt: now});
    }
    else {
      deployInfo.deployedAt = deployInfo.createdAt = now;

      return config.databaseController.create(
        className,
        this._patchDeployInfo(deployInfo));
    }
  }

  // Patch deployInfo to avoid keys invalid chars, like dots in filenames.
  _patchDeployInfo(deployInfo){
    deployInfo.files = [];
    Object.keys(deployInfo.userFiles).forEach(folder => {
      Object.keys(deployInfo.userFiles[folder]).forEach(filename => {
        var version = deployInfo.userFiles[folder][filename],
          checksum = deployInfo.checksums[folder][filename];
        deployInfo.files.push({
          folder: folder,
          filename: filename,
          version: version,
          checksum: checksum
        });
      });
    });
    delete deployInfo.userFiles;
    delete deployInfo.checksums;
    return deployInfo;
  }

  _unpatchDeployInfo(deployInfo){
    deployInfo.userFiles = {};
    deployInfo.checksums = {};

    deployInfo.files.forEach(obj => {
      deployInfo.userFiles[obj.folder] = deployInfo.userFiles[obj.folder] || {};
      deployInfo.userFiles[obj.folder][obj.filename] = obj.version;

      deployInfo.checksums[obj.folder] = deployInfo.checksums[obj.folder] || {};
      deployInfo.checksums[obj.folder][obj.filename] = obj.checksum;
    });
    delete deployInfo.files;
    return deployInfo;
  }

  _collect(appId, deployInfo, folder){
    var promises = Object.keys(deployInfo.userFiles[folder] || {})
    .map(filename => {
      var version = deployInfo.userFiles[folder][filename],
        checksum = deployInfo.checksums[folder][filename];
      return this.getFile(appId, folder, filename, version, checksum)
      .then(data => {
        return this.vendorAdapter.collect(
          appId, deployInfo, folder, filename, data);
      });
    });

    return Promise.all(promises);
  }

  deploy(appId, deployInfo){
    return this._processRollback(appId, deployInfo)
    .then(di => {
      deployInfo = di;
      return this._processFiles(appId, di);
    })
    .then(() => {
      deployInfo._checksum = computeChecksum(JSON.stringify(deployInfo));
      return deployInfo;
    })
    .then(di => this._setReleaseMetadata(appId, di))
    .then(() => this._comparePackageVersion(appId, deployInfo))
    .then((packageInfo) => { deployInfo.packageChanged =
      packageInfo.packageChanged; })
    .then(() => this._collect(appId, deployInfo, 'cloud'))
    .then(() => this._collect(appId, deployInfo, 'public'))
    .then(() => this.vendorAdapter.publish(appId, deployInfo))
    .then(() => {
      delete deployInfo._checksum;
      delete deployInfo.packageChanged;
      return deployInfo;
    })
    .then(() => {
      return this._setDeployInfo(appId, deployInfo);
    })
    .then(() => deployInfo);
  }

  _getNextReleaseId(appId) {
    return this._findDeployInfo(appId, {}, {releaseId: -1})
    .then(deployInfo => {
      let currentReleaseId = deployInfo ? deployInfo.releaseId : 0;
      return parseInt(currentReleaseId) + 1;
    });
  }

  _setReleaseMetadata(appId, deployInfo) {
    if (deployInfo.releaseId) {
      return Promise.resolve(deployInfo);
    }
    return this._getNextReleaseId(appId)
    .then(deployInfoId => {
      deployInfo.releaseId = deployInfoId;
      deployInfo.releaseName = "v" + deployInfoId;
      return deployInfo;
    });
  }

  _processRollback(appId, deployInfo){
    if (deployInfo.releaseName === undefined) {
      // it is not a rollback
      return Promise.resolve(deployInfo);
    }
    else if (deployInfo.releaseName === "") {
      // rollback to deploy before the current one
      return this.getDeployInfo(appId)
      .then(deployInfo => this._findDeployInfo(
        appId,
        {releaseId: {"$lt": deployInfo.releaseId}},
        {releaseId: -1}));
    }
    else {
      return this._findDeployInfo(
        appId,
        {releaseName: deployInfo.releaseName},
        {deployedAt: -1});
    }
  }

  _processFiles(appId, deployInfo){
    if (!deployInfo.files){
      return Promise.resolve(deployInfo);
    }
    if (deployInfo.checksums || deployInfo.userFiles) {
      throw new Error("Files must not be defined with checksums and userFiles.");
    }

    // Prevent deploy request error from dashboard.
    if (deployInfo.checksums === undefined) {
      deployInfo.checksums = {};
    }
    if (deployInfo.userFiles === undefined) {
      deployInfo.userFiles = {};
    }

    var promises = Object.keys(deployInfo.files)
    .map(folder => {
      var folderPromises = Object.keys(deployInfo.files[folder] || {})
      .map(filename => {
        let content = deployInfo.files[folder][filename];

        return this.uploadFile(appId, folder, filename, content)
          .then(obj => {
            deployInfo.checksums[folder] = deployInfo.checksums[folder] || {};
            deployInfo.checksums[folder][filename] = obj.checksum;
            deployInfo.userFiles[folder] = deployInfo.userFiles[folder] || {};
            deployInfo.userFiles[folder][filename] = obj.version;
          });
      });
      return Promise.all(folderPromises);
    });
    return Promise.all(promises).then(() => {
      delete deployInfo.files;
      return deployInfo;
    });
  }

  _comparePackageVersion(appId, deployInfo) {
    let packageInfo = {};
    return this.getDeployInfo(appId)
      .then(deployInfo => this._findDeployInfo(
        appId,
        {releaseId: {"$lt": deployInfo.releaseId}},
        {releaseId: -1}))
      .then(oldDeployInfo => this._processFiles(appId, oldDeployInfo || {}))
      .then(oldDeployInfo => {
        packageInfo.packageChanged =
          !oldDeployInfo.checksums ||
          deployInfo.checksums['cloud']['package.json'] !==
            oldDeployInfo.checksums['cloud']['package.json'];
        return packageInfo;
      })
      .then(() => {
        if (this.vendorAdapter.installModules && packageInfo.packageChanged) {
          return this.vendorAdapter.installModules(appId)
            .then(() => packageInfo);
        } else {
          packageInfo.packageChanged = false;
          return packageInfo;
        }
      });
  }

  getCollectionName(appId, collectionName) {
    return this.vendorAdapter.getCollectionName ?
      this.vendorAdapter.getCollectionName(appId, collectionName) :
      collectionName;
  }

  getBasePath(appId, basePath) {
    return this.vendorAdapter.getBasePath ?
      this.vendorAdapter.getBasePath(appId, basePath) :
      basePath;
  }

  getReleases(appId) {
    let config = AppCache.get(appId);
    return config.databaseController.find(
      this.getCollectionName(appId, DeployInfoCollectionName), {}, {
      sort: {createdAt: 1}
    })
    .then(results => {
      return results.map(deployInfo => {
        return this._unpatchDeployInfo(deployInfo);
      });
    })
    .then(results => {
      return results.map(deployInfo => {
        return {
          version: deployInfo.releaseName,
          description: deployInfo.description,
          timestamp: deployInfo.createdAt,
          // parse-cli expects a string for no reason
          userFiles: JSON.stringify(deployInfo.userFiles),
        };
      });
    });
  }
}

export default ParseCliController;
export {
    ParseCliController
};
<commit_msg>Moves to adapter<commit_after>import crypto from 'crypto';
import path from 'path';

import AppCache from 'parse-server/lib/cache';

const DeployInfoBasePath = "_deployInfo";
const DeployInfoCollectionName = "deployInfo";
const FilesCollectionName = "deployFile";

function computeChecksum(content){
    var algo = 'md5',
        md5sum = crypto.createHash(algo);
    md5sum.update(content, 'binary');
    return md5sum.digest('hex');
}

class ParseCliController {
  constructor(vendorAdapter: any){
    this.vendorAdapter = vendorAdapter;
  }

  getCliLatestVersion(){
    return this.vendorAdapter.getCliLatestVersion();
  }

  isSupported(mode, other, version){
    return new Promise((resolve, reject) => {
      // local commands must be defined
      var localCommands = [
        "default",
        "generate",
        "-h",
        "--help",
        "help",
        "migrate",
        "update",
        "version",
      ],
      commands = [
        "add",
        "configure accountkey",
        "deploy",
        "develop",
        "download",
        "functions",
        "jssdk",
        "list",
        "logs",
        "new",
        "releases",
        "rollback",
        "triggers",
      ],
      nokCommands = [
        "symbols",
      ],
      tokens = {};
      localCommands.forEach(value => {
        tokens[value] = true;
      });
      commands.forEach(value => {
        tokens[value] = true;
      });

      if (mode == 'parse') {
        var parts = other.split(" ");
        for (var i = 1; i <= parts.length; i++) {
          var token = parts.slice(0, i).join(" ");
          if (tokens[token]) {
            resolve({});
            return;
          }
        }
      }
      reject();
    });
  }

  getAccountKey(email, password){
    return this.vendorAdapter.getAccountKey(email, password);
  }
  getEmail(accountKey){
    return this.vendorAdapter.getEmail(accountKey);
  }

  getJsVersions(appId){
    return this.vendorAdapter.getJsVersions(appId);
  }

  getApps(accountKey) {
    return this.vendorAdapter.getApps(accountKey);
  }

  getApp(accountKey, appId) {
    return this.vendorAdapter.getApp(accountKey, appId);
  }

  createApp(accountKey, appName) {
    return this.vendorAdapter.createApp(accountKey, appName);
  }

  getFile(appId, folder, filename, version, checksum){
    let config = AppCache.get(appId),
        query = { folder, filename, version, checksum };
    return config.databaseController.find(
      this.getCollectionName(appId, FilesCollectionName),
      query)
    .then(objects => {
      if (objects.length == 0) {
        throw new Error("Object " + JSON.stringify(query) + " not found.");
      }
      var object = objects[0];
      return config.filesController.getFileData(config, object.name)
      .then(data => {
        // Data is base64 encoded
        if (object.encoding == 'base64') {
          data = new Buffer(data.toString(), 'base64');
        }
        return data;
      });
    });
  }

  uploadFile(appId, folder, filename, content){

    let config = AppCache.get(appId),
        key = path.join(folder, filename),
        binaryContent = new Buffer(content, 'base64').toString('binary'),
        checksum = computeChecksum(binaryContent),
        filesCollectionName = this.getCollectionName(appId, FilesCollectionName);

    return config.databaseController.find(filesCollectionName, {
        folder: folder,
        filename: filename
      }, {
        sort: {createdAt: 1}
      })
    .then(objects => {
      var nextVersion;

      if (objects.length == 0) {
        nextVersion = 1;
      }
      else {
        var object = objects[0],
            currentVersion = object.version;
        nextVersion = parseInt(currentVersion) + 1;
      }
      return nextVersion.toString();
    })
    .then(version => config.filesController.createFile(
        config,
        path.join(this.getBasePath(appId, DeployInfoBasePath), key),
        content)
      .then(obj => config.databaseController.create(filesCollectionName, {
        version: version,
        checksum: checksum,
        folder: folder,
        filename: filename,
        name: obj.name,
        url: obj.url,
        encoding: 'base64',
      }))
      .then(() => {
        return {
          checksum: checksum,
          version: version
        };
      })
    );
  }

  _findDeployInfo(appId, query, sort) {
    let config = AppCache.get(appId);
    let options = {
      sort: sort,
      limit: 1
    }
    return config.databaseController.find(
      this.getCollectionName(appId, DeployInfoCollectionName),
      query,
      options)
    .then(results => {
      return results.map(deployInfo => {
        if (this.vendorAdapter.filterFiles) {
          this.vendorAdapter.filterFiles(deployInfo);
        }
        return this._unpatchDeployInfo(deployInfo);
      });
    })
    .then(results => {
      return results.length !== 0 ? results[0] : null;
    });
  }

  getDeployInfo(appId){
    return this._findDeployInfo(appId, {}, {deployedAt: -1, createdAt: -1});
  }

  _setDeployInfo(appId, deployInfo){
    let config = AppCache.get(appId);
    let className = this.getCollectionName(appId, DeployInfoCollectionName);
    let query = {releaseId: deployInfo.releaseId};
    let now = new Date().toISOString();
    if (deployInfo.createdAt) {
      return config.databaseController.update(
        className, query, {deployedAt: now});
    }
    else {
      deployInfo.deployedAt = deployInfo.createdAt = now;

      return config.databaseController.create(
        className,
        this._patchDeployInfo(deployInfo));
    }
  }

  // Patch deployInfo to avoid keys invalid chars, like dots in filenames.
  _patchDeployInfo(deployInfo){
    deployInfo.files = [];
    Object.keys(deployInfo.userFiles).forEach(folder => {
      Object.keys(deployInfo.userFiles[folder]).forEach(filename => {
        var version = deployInfo.userFiles[folder][filename],
          checksum = deployInfo.checksums[folder][filename];
        deployInfo.files.push({
          folder: folder,
          filename: filename,
          version: version,
          checksum: checksum
        });
      });
    });
    delete deployInfo.userFiles;
    delete deployInfo.checksums;
    return deployInfo;
  }

  _unpatchDeployInfo(deployInfo){
    deployInfo.userFiles = {};
    deployInfo.checksums = {};

    deployInfo.files.forEach(obj => {
      deployInfo.userFiles[obj.folder] = deployInfo.userFiles[obj.folder] || {};
      deployInfo.userFiles[obj.folder][obj.filename] = obj.version;

      deployInfo.checksums[obj.folder] = deployInfo.checksums[obj.folder] || {};
      deployInfo.checksums[obj.folder][obj.filename] = obj.checksum;
    });
    delete deployInfo.files;
    return deployInfo;
  }

  _collect(appId, deployInfo, folder){
    var promises = Object.keys(deployInfo.userFiles[folder] || {})
    .map(filename => {
      var version = deployInfo.userFiles[folder][filename],
        checksum = deployInfo.checksums[folder][filename];
      return this.getFile(appId, folder, filename, version, checksum)
      .then(data => {
        return this.vendorAdapter.collect(
          appId, deployInfo, folder, filename, data);
      });
    });

    return Promise.all(promises);
  }

  deploy(appId, deployInfo){
    return this._processRollback(appId, deployInfo)
    .then(di => {
      deployInfo = di;
      return this._processFiles(appId, di);
    })
    .then(() => {
      deployInfo._checksum = computeChecksum(JSON.stringify(deployInfo));
      return deployInfo;
    })
    .then(di => this._setReleaseMetadata(appId, di))
    .then(() => this._comparePackageVersion(appId, deployInfo))
    .then((packageInfo) => { deployInfo.packageChanged =
      packageInfo.packageChanged; })
    .then(() => this._collect(appId, deployInfo, 'cloud'))
    .then(() => this._collect(appId, deployInfo, 'public'))
    .then(() => this.vendorAdapter.publish(appId, deployInfo))
    .then(() => {
      delete deployInfo._checksum;
      delete deployInfo.packageChanged;
      return deployInfo;
    })
    .then(() => {
      return this._setDeployInfo(appId, deployInfo);
    })
    .then(() => deployInfo);
  }

  _getNextReleaseId(appId) {
    return this._findDeployInfo(appId, {}, {releaseId: -1})
    .then(deployInfo => {
      let currentReleaseId = deployInfo ? deployInfo.releaseId : 0;
      return parseInt(currentReleaseId) + 1;
    });
  }

  _setReleaseMetadata(appId, deployInfo) {
    if (deployInfo.releaseId) {
      return Promise.resolve(deployInfo);
    }
    return this._getNextReleaseId(appId)
    .then(deployInfoId => {
      deployInfo.releaseId = deployInfoId;
      deployInfo.releaseName = "v" + deployInfoId;
      return deployInfo;
    });
  }

  _processRollback(appId, deployInfo){
    if (deployInfo.releaseName === undefined) {
      // it is not a rollback
      return Promise.resolve(deployInfo);
    }
    else if (deployInfo.releaseName === "") {
      // rollback to deploy before the current one
      return this.getDeployInfo(appId)
      .then(deployInfo => this._findDeployInfo(
        appId,
        {releaseId: {"$lt": deployInfo.releaseId}},
        {releaseId: -1}));
    }
    else {
      return this._findDeployInfo(
        appId,
        {releaseName: deployInfo.releaseName},
        {deployedAt: -1});
    }
  }

  _processFiles(appId, deployInfo){
    if (!deployInfo.files){
      return Promise.resolve(deployInfo);
    }
    if (deployInfo.checksums || deployInfo.userFiles) {
      throw new Error("Files must not be defined with checksums and userFiles.");
    }

    // Prevent deploy request error from dashboard.
    if (deployInfo.checksums === undefined) {
      deployInfo.checksums = {};
    }
    if (deployInfo.userFiles === undefined) {
      deployInfo.userFiles = {};
    }

    var promises = Object.keys(deployInfo.files)
    .map(folder => {
      var folderPromises = Object.keys(deployInfo.files[folder] || {})
      .map(filename => {
        let content = deployInfo.files[folder][filename];

        return this.uploadFile(appId, folder, filename, content)
          .then(obj => {
            deployInfo.checksums[folder] = deployInfo.checksums[folder] || {};
            deployInfo.checksums[folder][filename] = obj.checksum;
            deployInfo.userFiles[folder] = deployInfo.userFiles[folder] || {};
            deployInfo.userFiles[folder][filename] = obj.version;
          });
      });
      return Promise.all(folderPromises);
    });
    return Promise.all(promises).then(() => {
      delete deployInfo.files;
      return deployInfo;
    });
  }

  _comparePackageVersion(appId, deployInfo) {
    let packageInfo = {};
    return this.getDeployInfo(appId)
      .then(deployInfo => this._findDeployInfo(
        appId,
        {releaseId: {"$lt": deployInfo.releaseId}},
        {releaseId: -1}))
      .then(oldDeployInfo => this._processFiles(appId, oldDeployInfo || {}))
      .then(oldDeployInfo => {
        packageInfo.packageChanged =
          !oldDeployInfo.checksums ||
          deployInfo.checksums['cloud']['package.json'] !==
            oldDeployInfo.checksums['cloud']['package.json'];
        return packageInfo;
      })
      .then(() => {
        if (this.vendorAdapter.installModules && packageInfo.packageChanged) {
          return this.vendorAdapter.installModules(appId)
            .then(() => packageInfo);
        } else {
          packageInfo.packageChanged = false;
          return packageInfo;
        }
      });
  }

  getCollectionName(appId, collectionName) {
    return this.vendorAdapter.getCollectionName ?
      this.vendorAdapter.getCollectionName(appId, collectionName) :
      collectionName;
  }

  getBasePath(appId, basePath) {
    return this.vendorAdapter.getBasePath ?
      this.vendorAdapter.getBasePath(appId, basePath) :
      basePath;
  }

  getReleases(appId) {
    let config = AppCache.get(appId);
    return config.databaseController.find(
      this.getCollectionName(appId, DeployInfoCollectionName), {}, {
      sort: {createdAt: 1}
    })
    .then(results => {
      return results.map(deployInfo => {
        return this._unpatchDeployInfo(deployInfo);
      });
    })
    .then(results => {
      return results.map(deployInfo => {
        return {
          version: deployInfo.releaseName,
          description: deployInfo.description,
          timestamp: deployInfo.createdAt,
          // parse-cli expects a string for no reason
          userFiles: JSON.stringify(deployInfo.userFiles),
        };
      });
    });
  }
}

export default ParseCliController;
export {
    ParseCliController
};
<|endoftext|>


<commit_before>import {Message} from '../common';

// +----------------------------------------+
// |  CONNECT www.bing.com:443 HTTP/1.1\r\n |
// |  Host: www.bing.com:443\r\n            |
// |  [...Headers]                          |
// |  \r\n                                  |
// +----------------------------------------+
export class RequestMessage extends Message {

  METHOD;

  URI;

  VERSION;

  HOST;

  constructor(options = {}) {
    super();
    const fields = {
      ...options
    };
    this.METHOD = fields.METHOD;
    this.URI = fields.URI;
    this.VERSION = fields.VERSION;
    this.HOST = fields.HOST;
  }

  static parse(buffer) {
    if (buffer.length < 20) {
      return null;
    }
    const str = buffer.toString();
    const lines = str.split('\r\n');
    const [method, uri, version] = lines[0].split(' ');
    const host = lines[1].split(' ')[1];
    const methods = [
      'OPTIONS', 'GET', 'HEAD', 'POST',
      'PUT', 'DELETE', 'TRACE', 'CONNECT'
    ];
    if (methods.includes(method)) {
      return new RequestMessage({
        METHOD: Buffer.from(method),
        URI: Buffer.from(uri),
        VERSION: Buffer.from(version),
        HOST: Buffer.from(host)
      });
    }
    return null;
  }

}
<commit_msg>fix(http): switch between Socks5 and HTTP can cause TypeError<commit_after>import {Message} from '../common';

// +----------------------------------------+
// |  CONNECT www.bing.com:443 HTTP/1.1\r\n |
// |  Host: www.bing.com:443\r\n            |
// |  [...Headers]                          |
// |  \r\n                                  |
// +----------------------------------------+
export class RequestMessage extends Message {

  METHOD;

  URI;

  VERSION;

  HOST;

  constructor(options = {}) {
    super();
    const fields = {
      ...options
    };
    this.METHOD = fields.METHOD;
    this.URI = fields.URI;
    this.VERSION = fields.VERSION;
    this.HOST = fields.HOST;
  }

  static parse(buffer) {
    if (buffer.length < 20) {
      return null;
    }
    const str = buffer.toString();
    const lines = str.split('\r\n');

    if (lines.length < 2) {
      return null;
    }

    const [method, uri, version] = lines[0].split(' ');
    const methods = [
      'OPTIONS', 'GET', 'HEAD', 'POST',
      'PUT', 'DELETE', 'TRACE', 'CONNECT'
    ];
    if (methods.includes(method)) {
      const host = lines[1].split(' ')[1];
      return new RequestMessage({
        METHOD: Buffer.from(method),
        URI: Buffer.from(uri),
        VERSION: Buffer.from(version),
        HOST: Buffer.from(host)
      });
    }
    return null;
  }

}
<|endoftext|>


<commit_before>'use strict';

var directive = {};
var service = { value: {} };

var DEPENDENCIES = {
  'angular.js': 'http://code.angularjs.org/angular-' + angular.version.full + '.min.js',
  'angular-resource.js': 'http://code.angularjs.org/angular-resource-' + angular.version.full + '.min.js',
  'angular-sanitize.js': 'http://code.angularjs.org/angular-sanitize-' + angular.version.full + '.min.js',
  'angular-cookies.js': 'http://code.angularjs.org/angular-cookies-' + angular.version.full + '.min.js'
};

function escape(text) {
  return text.
    replace(/\&/g, '&').
    replace(/\</g, '<').
    replace(/\>/g, '>').
    replace(/"/g, '"');
}

/**
 * http://stackoverflow.com/questions/451486/pre-tag-loses-line-breaks-when-setting-innerhtml-in-ie
 * http://stackoverflow.com/questions/195363/inserting-a-newline-into-a-pre-tag-ie-javascript
 */
function setHtmlIe8SafeWay(element, html) {
  var newElement = angular.element('
' + html + '
');

  element.html('');
  element.append(newElement.contents());
  return element;
}

directive.jsFiddle = function(getEmbeddedTemplate, escape, script) {
  return {
    terminal: true,
    link: function(scope, element, attr) {
      var name = '',
        stylesheet = '<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css">\n',
        fields = {
          html: '',
          css: '',
          js: ''
        };

      angular.forEach(attr.jsFiddle.split(' '), function(file, index) {
        var fileType = file.split('.')[1];

        if (fileType == 'html') {
          if (index == 0) {
            fields[fileType] +=
              '
\n' +
                getEmbeddedTemplate(file, 2);
          } else {
            fields[fileType] += '\n\n\n  \n' +
              '  \n';
          }
        } else {
          fields[fileType] += getEmbeddedTemplate(file) + '\n';
        }
      });

      fields.html += '
\n';

      setHtmlIe8SafeWay(element,
        '<form class="jsfiddle" method="post" action="http://jsfiddle.net/api/post/library/pure/" target="_blank">' +
          hiddenField('title', 'AngularJS Example: ' + name) +
          hiddenField('css', '</style>  \n' +
            stylesheet +
            script.angular +
            (attr.resource ? script.resource : '') +
            '  \n' +
            stylesheet +
            script.angular +
            (attr.resource ? script.resource : '') +
            '


<commit_before>var HyperdeckCore = require("./hyperdeck-core.js");

var Hyperdeck = function(ip) {
  //Start by connecting to Hyperdeck via HypderdeckCore
  var Core = new HyperdeckCore(ip);
  Core.makeRequest("notify: remote: true");
  Core.makeRequest("notify: transport: true");
  Core.makeRequest("notify: slot: true");
  Core.makeRequest("notify: configuration: true");

  //Publicise functions from Core
  this.getNotifier = Core.getNotifier();
  this.makeRequest = Core.makeRequest();
  this.onConnected = Core.onConnected();

  //make Easy Access commands
  this.play = function(speed) {
    var commandString;
    if (Math.abs(speed) <= 1600) {
      commandString = "play speed: " + speed;
    } else {
      if (speed) {
        throw new Error("Speed value invalid or out of range");
      } else {
        commandString = "play";
      }
    }
    return Core.makeRequest(commandString);
  };

  this.stop = function() {
    return Core.makeRequest("stop");
  };

  this.record = function() {
    return Core.makeRequest("record");
  };

  this.goTo = function(timecode) {
    return Core.makeRequest("goto: timecode: " + timecode);
  };

  this.slotInfo = function (id) {
    if (id === 0 || id === 1 || id === 2){ //TO DO find if it's 0-1 or 1-2
      return Core.makeRequest("slot info: slot id: " + id);
    } else{
      if (!id){
        return Core.makeRequest("slot info");
      }
      return new Error("Slot ID Value out of range");
    }
  };

  this.transportInfo = function(){
    return Core.makeRequest("transport info");
  };

  this.clipsGet = function(){
    return Core.makeRequest("clips get");
  };

  this.slotSelect = function(id){
    return Core.makeRequest("slot select: slot id: " + id);
  };

};

module.exports = Hyperdeck;
<commit_msg>Make Hyperdeck extend HyperdeckCore<commit_after>var util = require('util');
var HyperdeckCore = require("./hyperdeck-core.js");

var Hyperdeck = function(ip) {

  // call constructor of HyperdeckCore
  HyperdeckCore.call(this, ip);

  this.makeRequest("notify: remote: true");
  this.makeRequest("notify: transport: true");
  this.makeRequest("notify: slot: true");
  this.makeRequest("notify: configuration: true");

  // add Easy Access commands
  this.play = function(speed) {
    var commandString;
    if (Math.abs(speed) <= 1600) {
      commandString = "play speed: " + speed;
    } else {
      if (speed) {
        throw new Error("Speed value invalid or out of range");
      } else {
        commandString = "play";
      }
    }
    return this.makeRequest(commandString);
  };

  this.stop = function() {
    return this.makeRequest("stop");
  };

  this.record = function() {
    return this.makeRequest("record");
  };

  this.goTo = function(timecode) {
    return this.makeRequest("goto: timecode: " + timecode);
  };

  this.slotInfo = function (id) {
    if (id === 0 || id === 1 || id === 2){ //TO DO find if it's 0-1 or 1-2
      return this.makeRequest("slot info: slot id: " + id);
    } else{
      if (!id){
        return this.makeRequest("slot info");
      }
      throw new Error("Slot ID Value out of range");
    }
  };

  this.transportInfo = function(){
    return this.makeRequest("transport info");
  };

  this.clipsGet = function(){
    return this.makeRequest("clips get");
  };

  this.slotSelect = function(id){
    return this.makeRequest("slot select: slot id: " + id);
  };

};

// make this class extend HyperdeckCore
// https://nodejs.org/docs/latest/api/util.html#util_util_inherits_constructor_superconstructor
util.inherits(Hyperdeck, HyperdeckCore);

module.exports = Hyperdeck;
<|endoftext|>


<commit_before>/**
 * @file TouchRipple
 * @author leon <ludafa@outlook.com>
 */

import san from 'san';
import Ripple from './Ripple';

export default san.defineComponent({

    components: {
        'san-ripple': Ripple
    },

    template: `
        <div
            class="sm-touch-ripple"
            on-click="click($event)">
            <san-ripple
                san-for="ripple, index in ripples"
                left="{{ripple.x}}"
                top="{{ripple.y}}"
                width="{{ripple.width}}"
                height="{{ripple.height}}"
                on-animate-end="onRippleAnimateEnd(index)" />
        </div>
    `,

    initData() {
        return {
            ripples: []
        };
    },

    click(e) {
        let {pageX, pageY} = e;
        let {top, left, width, height} = this.el.getBoundingClientRect();
        this.data.push('ripples', {
            x: pageX - left,
            y: pageY - top,
            width,
            height
        });
    },

    onRippleAnimateEnd(rippleIndex) {
        this.data.removeAt('ripples', rippleIndex);
    }

});
<commit_msg>fix ripple position error<commit_after>/**
 * @file TouchRipple
 * @author leon <ludafa@outlook.com>
 */

import san from 'san';
import Ripple from './Ripple';

export default san.defineComponent({

    components: {
        'san-ripple': Ripple
    },

    template: `
        <div
            class="sm-touch-ripple"
            on-click="click($event)">
            <san-ripple
                san-for="ripple, index in ripples"
                left="{{ripple.x}}"
                top="{{ripple.y}}"
                width="{{ripple.width}}"
                height="{{ripple.height}}"
                on-animate-end="onRippleAnimateEnd(index)" />
        </div>
    `,

    initData() {
        return {
            ripples: []
        };
    },

    click(e) {
        let {clientX, clientY} = e;
        let {top, left, width, height} = this.el.getBoundingClientRect();
        this.data.push('ripples', {
            x: clientX - left,
            y: clientY - top,
            width,
            height
        });
    },

    onRippleAnimateEnd(rippleIndex) {
        this.data.removeAt('ripples', rippleIndex);
    }

});
<|endoftext|>


<commit_before>var fs = require('fs-extra');
var watch = require("fwatcher");
var zlib = require('zlib');
var mime = require('mime');
var crypto = require('crypto');
var minifyHtml = require('html-minifier').minify;
var minifyCSS =  require('clean-css');
var UglifyJS = require('uglify-js');
var Cache = require('./cache');
var utils = require('./client/utils');

var fileCache = Cache('files');
var html_minifier_options = {
    removeComments: true,
    removeCommentsFromCDATA: true,
    removeCDATASectionsFromCDATA: true,
    removeAttributeQuotes: true,
    removeRedundantAttributes: true,
    removeEmptyAttributes: true,
    removeOptionalTags: true,
    collapseBooleanAttributes: true,
    collapseWhitespace: true
};

// regular expressions
var suffix = /\.\w+$/;
var isWorker = /^\(W|S\)/;
var checkFp = /\.@[a-z0-9]{7}\.(?:js|css|html)$/i;
var replaceDoubleSlash = /\/{2,}/g;

// TODO stream data files into and from cache

/**
 * Prepare external component file paths.
 *
 * @public
 * @param {object} The load context, ex a module package.
 * @param {function} The callback function.
 */
exports.prepare = function (components, callback) {

    // check input data
    var files = {};
    var lastScript;

    if (components.scripts instanceof Array) {
        lastScript = components.scripts[0];
        files.scripts = components.scripts;
    }

    if (components.styles instanceof Array) {
        files.styles = components.styles;
    }

    if (components.markup instanceof Array) {
        files.markup = components.markup;
    }

    // return components object, if there are no files to prepare
    if (!files.scripts && !files.styles && !files.markup) {
        return utils.nextTick(callback, null, components);
    }

    // set apps public folder as default base path
    var base = components.base || engine.paths.app_public;

    var countDown = 0;
    var combinedFile = {
        base: base,
        path: lastScript,
        read: base + lastScript,
        maxAge: engine.http.maxAge_fingerprint,
        module: components.module,
        related: components.related,
        data: '',
        files: []
    };

    var handler = function (err, file, context) {

        if (err) {

            // log error
            engine.log('E', err);

            if (--countDown === 0) {
                callback(null, components);
            }
            return;
        }

        // collect files to combine
        if (engine.production && context.type === 'scripts' && file.data) {
            combinedFile.files.push(file);

        } else {
            files[context.type][context.index] = file.ext;
        }

        if (--countDown === 0) {

            if (engine.production && files.scripts && combinedFile) {

                // combine files
                for (var i = 0, l = combinedFile.files.length; i < l; ++i) {

                    // concatenate data
                    combinedFile.data = combinedFile.files[i].data + combinedFile.data;

                    // remove file from cache
                    fileCache.rm(combinedFile.files[i].read);
                }

                createFileDescriptor(combinedFile, combinedFile.data, function (err, file) {

                    if (err) {
                        return callback(engine.log('E', err));
                    }

                    // overwrite module resources
                    files.scripts = [file.ext];

                    callback(null, files);
                });

                return;
            }

            callback(null, files);
        }
    };

    // get file data
    var type, i, l, context;
    for (type in files) {

        // get file count
        countDown += l = files[type].length;

        for (i = 0; i < l; ++i) {

            context = {
                type: type,
                index: i
            };

            // ignore external files
            if (files[type][i].indexOf('//') > -1) {
                utils.nextTick(handler, null, {ext: files[type][i]}, context);
                continue;
            }

            // get worker info
            worker = false;
            if (isWorker.test(files[type][i])) {
                worker = files[type][i].substr(1,1);
                files[type][i] = files[type][i].substr(3);
            }

            this.get({
                path: files[type][i],
                // set public app folder for public files and the markup folder for markup files
                base: files[type][i][0] === '/' ? (type === 'markup' ? engine.paths.app_markup : engine.paths.app_public) : base,
                wrap: files[type][i] === 'resource.js' || worker ? false : true,
                module: components.module,
                maxAge: engine.http.maxAge_fingerprint,
                noCompression: engine.production && type === 'scripts' ? true : false,
                related: components.related,
                worker: worker
            }, handler, context);
        }
    }
};

/**
 * Read and parse a JSON file.
 */
exports.json = function (path, callback, type, key) {

    fs.readJson(path, function (err, object) {

        if (err) {
            return callback(engine.log('E', err));
        }

        if (!object) {
            return callback(engine.log('E', new Error('JSON file "' + path + '" not found.')));
        }

        // watch file changes
        if (!engine.production) {
            watch(path, true, function (err, event, path) {
                handleFileChange(err, event, path, type, key);
            });
        }

        callback(null, object);
    });
};

/**
 * Read a file or get it from the cache.
 *
 * @public
 * @param {mixed} The file descriptor or path.
 * @param {function} The callback function.
 * @param {object} An object with context informations.
 */
exports.get = function (file, callback, context) {

    // check file
    if (!file.path || !file.base) {
        return utils.nextTick(callback, new Error('Missing file path or file base.'), undefined, context);
    }

    // create a clean path without the fingerprint
    file.path = removeFingerprint(file.path);

    // create absolute path
    file.read = (file.base + file.path).replace(replaceDoubleSlash, '/');

    // get file from cache
    var cachedFile = fileCache.get(file.read);
    if (cachedFile) {

        // add related instances, composition, modules and update cache
        if (file.related) {
            cachedFile = this.relate(file.related, cachedFile);
            fileCache.set(file.read, cachedFile);
        }

        return utils.nextTick(callback, null, cachedFile, context);
    }

    // read the file from the file system
    fs.readFile(file.read, function (err, data) {

        if (err) {
            return callback(engine.log('E', err), undefined, context);
        }

        // get the modification time
        fs.stat(file.read, function (err, stats) {

            if (err) {
                return callback(engine.log('E', err), undefined, context);
            }

            // watch file changes
            if (!engine.production) {
                watch(file.read, true, handleFileChange);
            }

            file.mtime = stats.mtime;

            createFileDescriptor(file, data, callback, context);
        });
    });
};

exports.relate = function (related, object) {

    // ensure related object
    object.related = object.related || {};

    // merge related items to object
    var type, key;
    for (type in related) {
        object.related[type] = object.related[type] || {};
        for (key in related[type]) {
            object.related[type][key] = true;
        }
    }

    return object;
};

/**
 * Emit file path on file change.
 */
function handleFileChange (err, event, path, type, key) {

    if (err) {
        return engine.log('E', err);
    }

    key = key || path;
    type = type || 'files';

    // get cache
    var cache = Cache(type);
    var item = cache.get(key);

    if (!item) {
        return;
    }

    var log = {
        file: key,
        removed: [{
            cache: type,
            item: key
        }]
    };

    // remove item itself
    cache.rm(key);

    // remove related items
    if (item.related) {
        for (type in item.related) {

            if (type === 'instances') {
                cache = engine.instances;
            } else if (!(cache = Cache(type))) {
                continue;
            }

            // remove items
            for (key in item.related[type]) {
                cache.rm ? cache.rm(key) : delete engine.instances[key];
                log.removed.push({
                    cache: type,
                    item: key
                });
            }
        }
    }

    engine.log('D', log, 'File changed.');
}

/**
 * Minify, compress, create fingerprint and save file in cache.
 */
function createFileDescriptor (file, data, callback, context) {

    // get file type
    file.type = mime.lookup(file.path);

    // append slash to module id or reset module value if it's a public path
    if (file.module) {
        file.module = file.path[0] === '/' ? '' : file.module + '/';
    }

    // minify js, html or css
    switch (file.type) {
        case 'text/html':

            // TODO don't compress as long html imports or binary ws data is available
            file.noCompression = true;
            data = minifyHtml(data.toString(), html_minifier_options);
            break;

        case 'text/css':
            if (engine.production) {
                data = new minifyCSS({processImport: false}).minify(data.toString()).styles;
            }
            break;

        case 'application/javascript':

            // wrap javascript files, except the engine client
            if (file.wrap) {
                data = "E('" + ((file.module || '')  + file.path) + "',function(require,module,exports,global,engine){\n" + data.toString() + "\nreturn module});";
            }

            if (engine.production && (data = UglifyJS.minify(data.toString(), {fromString: true}))) {
                data = data.code;
            }
            break;
    }

    // create fingerprint
    file.fp = crypto.createHash('md5').update(data).digest('hex').substr(0, 7);

    // create external path
    if (file.type === 'text/html') {
        file.ext = file.path;
    } else if (suffix.test(file.path)) {
        file.ext = file.path.split('.');
        file.ext.splice(-1, 0, '@' + file.fp);
        file.ext = file.ext.join('.');
    } else {
        file.ext = file.path + '.@' + file.fp;
    }

    // prepend module id to path
    file.ext = (file.worker ? '(' + file.worker + ')' : '') + (file.module || '') + file.ext;

    // add the data to the file object
    file.data = data;

    // don't compress data
    if (file.noCompression) {
        return createCacheObject(file, callback, context);
    }

    zlib.gzip(file.data, {level: zlib.Z_DEFAULT_COMPRESSION}, function (err, data) {

        if (err) {
            return callback(engine.log('E', err), undefined, context);
        }

        // update file data with compressed data
        file.data = data;

        createCacheObject(file, callback, context);
    });
}

/**
 * Create an cache object with the data buffer and the http headers.
 *
 * @private
 */
function createCacheObject (file, callback, context) {

    // create http headers
    // TODO send not modified for non scrips and css
    file.http = {
        'Vary': 'Accept-Encoding',
        'Cache-Control': 'public, max-age=' + (file.maxAge || engine.http.maxAge),
        'Content-Length': file.data.length,
        'Content-Type': file.type,
        'Etag': file.fp
    };

    // set last modified header
    if (file.mtime) {
        file.http['Last-Modified'] = file.mtime;
    }

    // send encoding header
    if (!file.noCompression) {
        file.http['Content-Encoding'] = 'gzip';
    }

    // save zipped data in cache
    fileCache.set(file.read, file);

    // return data
    callback(null, file, context);
}

/**
 * Remove fingerprint from path
 *
 * @private
 * @param {string} The file path.
 */
function removeFingerprint (path) {

    // remove fingerprint if it's found in path
    if (checkFp.test(path)) {

        // split path
        path = path.split('.');

        // remove fingerprint from read path
        path.splice(path.length - 2, 1);

        // create path without fingerprint
        path = path.join('.');
    }

    // return path
    return path;
}
<commit_msg>log file changes on info level<commit_after>var fs = require('fs-extra');
var watch = require("fwatcher");
var zlib = require('zlib');
var mime = require('mime');
var crypto = require('crypto');
var minifyHtml = require('html-minifier').minify;
var minifyCSS =  require('clean-css');
var UglifyJS = require('uglify-js');
var Cache = require('./cache');
var utils = require('./client/utils');

var fileCache = Cache('files');
var html_minifier_options = {
    removeComments: true,
    removeCommentsFromCDATA: true,
    removeCDATASectionsFromCDATA: true,
    removeAttributeQuotes: true,
    removeRedundantAttributes: true,
    removeEmptyAttributes: true,
    removeOptionalTags: true,
    collapseBooleanAttributes: true,
    collapseWhitespace: true
};

// regular expressions
var suffix = /\.\w+$/;
var isWorker = /^\(W|S\)/;
var checkFp = /\.@[a-z0-9]{7}\.(?:js|css|html)$/i;
var replaceDoubleSlash = /\/{2,}/g;

// TODO stream data files into and from cache

/**
 * Prepare external component file paths.
 *
 * @public
 * @param {object} The load context, ex a module package.
 * @param {function} The callback function.
 */
exports.prepare = function (components, callback) {

    // check input data
    var files = {};
    var lastScript;

    if (components.scripts instanceof Array) {
        lastScript = components.scripts[0];
        files.scripts = components.scripts;
    }

    if (components.styles instanceof Array) {
        files.styles = components.styles;
    }

    if (components.markup instanceof Array) {
        files.markup = components.markup;
    }

    // return components object, if there are no files to prepare
    if (!files.scripts && !files.styles && !files.markup) {
        return utils.nextTick(callback, null, components);
    }

    // set apps public folder as default base path
    var base = components.base || engine.paths.app_public;

    var countDown = 0;
    var combinedFile = {
        base: base,
        path: lastScript,
        read: base + lastScript,
        maxAge: engine.http.maxAge_fingerprint,
        module: components.module,
        related: components.related,
        data: '',
        files: []
    };

    var handler = function (err, file, context) {

        if (err) {

            // log error
            engine.log('E', err);

            if (--countDown === 0) {
                callback(null, components);
            }
            return;
        }

        // collect files to combine
        if (engine.production && context.type === 'scripts' && file.data) {
            combinedFile.files.push(file);

        } else {
            files[context.type][context.index] = file.ext;
        }

        if (--countDown === 0) {

            if (engine.production && files.scripts && combinedFile) {

                // combine files
                for (var i = 0, l = combinedFile.files.length; i < l; ++i) {

                    // concatenate data
                    combinedFile.data = combinedFile.files[i].data + combinedFile.data;

                    // remove file from cache
                    fileCache.rm(combinedFile.files[i].read);
                }

                createFileDescriptor(combinedFile, combinedFile.data, function (err, file) {

                    if (err) {
                        return callback(engine.log('E', err));
                    }

                    // overwrite module resources
                    files.scripts = [file.ext];

                    callback(null, files);
                });

                return;
            }

            callback(null, files);
        }
    };

    // get file data
    var type, i, l, context;
    for (type in files) {

        // get file count
        countDown += l = files[type].length;

        for (i = 0; i < l; ++i) {

            context = {
                type: type,
                index: i
            };

            // ignore external files
            if (files[type][i].indexOf('//') > -1) {
                utils.nextTick(handler, null, {ext: files[type][i]}, context);
                continue;
            }

            // get worker info
            worker = false;
            if (isWorker.test(files[type][i])) {
                worker = files[type][i].substr(1,1);
                files[type][i] = files[type][i].substr(3);
            }

            this.get({
                path: files[type][i],
                // set public app folder for public files and the markup folder for markup files
                base: files[type][i][0] === '/' ? (type === 'markup' ? engine.paths.app_markup : engine.paths.app_public) : base,
                wrap: files[type][i] === 'resource.js' || worker ? false : true,
                module: components.module,
                maxAge: engine.http.maxAge_fingerprint,
                noCompression: engine.production && type === 'scripts' ? true : false,
                related: components.related,
                worker: worker
            }, handler, context);
        }
    }
};

/**
 * Read and parse a JSON file.
 */
exports.json = function (path, callback, type, key) {

    fs.readJson(path, function (err, object) {

        if (err) {
            return callback(engine.log('E', err));
        }

        if (!object) {
            return callback(engine.log('E', new Error('JSON file "' + path + '" not found.')));
        }

        // watch file changes
        if (!engine.production) {
            watch(path, true, function (err, event, path) {
                handleFileChange(err, event, path, type, key);
            });
        }

        callback(null, object);
    });
};

/**
 * Read a file or get it from the cache.
 *
 * @public
 * @param {mixed} The file descriptor or path.
 * @param {function} The callback function.
 * @param {object} An object with context informations.
 */
exports.get = function (file, callback, context) {

    // check file
    if (!file.path || !file.base) {
        return utils.nextTick(callback, new Error('Missing file path or file base.'), undefined, context);
    }

    // create a clean path without the fingerprint
    file.path = removeFingerprint(file.path);

    // create absolute path
    file.read = (file.base + file.path).replace(replaceDoubleSlash, '/');

    // get file from cache
    var cachedFile = fileCache.get(file.read);
    if (cachedFile) {

        // add related instances, composition, modules and update cache
        if (file.related) {
            cachedFile = this.relate(file.related, cachedFile);
            fileCache.set(file.read, cachedFile);
        }

        return utils.nextTick(callback, null, cachedFile, context);
    }

    // read the file from the file system
    fs.readFile(file.read, function (err, data) {

        if (err) {
            return callback(engine.log('E', err), undefined, context);
        }

        // get the modification time
        fs.stat(file.read, function (err, stats) {

            if (err) {
                return callback(engine.log('E', err), undefined, context);
            }

            // watch file changes
            if (!engine.production) {
                watch(file.read, true, handleFileChange);
            }

            file.mtime = stats.mtime;

            createFileDescriptor(file, data, callback, context);
        });
    });
};

exports.relate = function (related, object) {

    // ensure related object
    object.related = object.related || {};

    // merge related items to object
    var type, key;
    for (type in related) {
        object.related[type] = object.related[type] || {};
        for (key in related[type]) {
            object.related[type][key] = true;
        }
    }

    return object;
};

/**
 * Emit file path on file change.
 */
function handleFileChange (err, event, path, type, key) {

    if (err) {
        return engine.log('E', err);
    }

    key = key || path;
    type = type || 'files';

    // get cache
    var cache = Cache(type);
    var item = cache.get(key);

    if (!item) {
        return;
    }

    var log = {
        file: key,
        removed: [{
            cache: type,
            item: key
        }]
    };

    // remove item itself
    cache.rm(key);

    // remove related items
    if (item.related) {
        for (type in item.related) {

            if (type === 'instances') {
                cache = engine.instances;
            } else if (!(cache = Cache(type))) {
                continue;
            }

            // remove items
            for (key in item.related[type]) {
                cache.rm ? cache.rm(key) : delete engine.instances[key];
                log.removed.push({
                    cache: type,
                    item: key
                });
            }
        }
    }

    engine.log('I', log, 'File changed.');
}

/**
 * Minify, compress, create fingerprint and save file in cache.
 */
function createFileDescriptor (file, data, callback, context) {

    // get file type
    file.type = mime.lookup(file.path);

    // append slash to module id or reset module value if it's a public path
    if (file.module) {
        file.module = file.path[0] === '/' ? '' : file.module + '/';
    }

    // minify js, html or css
    switch (file.type) {
        case 'text/html':

            // TODO don't compress as long html imports or binary ws data is available
            file.noCompression = true;
            data = minifyHtml(data.toString(), html_minifier_options);
            break;

        case 'text/css':
            if (engine.production) {
                data = new minifyCSS({processImport: false}).minify(data.toString()).styles;
            }
            break;

        case 'application/javascript':

            // wrap javascript files, except the engine client
            if (file.wrap) {
                data = "E('" + ((file.module || '')  + file.path) + "',function(require,module,exports,global,engine){\n" + data.toString() + "\nreturn module});";
            }

            if (engine.production && (data = UglifyJS.minify(data.toString(), {fromString: true}))) {
                data = data.code;
            }
            break;
    }

    // create fingerprint
    file.fp = crypto.createHash('md5').update(data).digest('hex').substr(0, 7);

    // create external path
    if (file.type === 'text/html') {
        file.ext = file.path;
    } else if (suffix.test(file.path)) {
        file.ext = file.path.split('.');
        file.ext.splice(-1, 0, '@' + file.fp);
        file.ext = file.ext.join('.');
    } else {
        file.ext = file.path + '.@' + file.fp;
    }

    // prepend module id to path
    file.ext = (file.worker ? '(' + file.worker + ')' : '') + (file.module || '') + file.ext;

    // add the data to the file object
    file.data = data;

    // don't compress data
    if (file.noCompression) {
        return createCacheObject(file, callback, context);
    }

    zlib.gzip(file.data, {level: zlib.Z_DEFAULT_COMPRESSION}, function (err, data) {

        if (err) {
            return callback(engine.log('E', err), undefined, context);
        }

        // update file data with compressed data
        file.data = data;

        createCacheObject(file, callback, context);
    });
}

/**
 * Create an cache object with the data buffer and the http headers.
 *
 * @private
 */
function createCacheObject (file, callback, context) {

    // create http headers
    // TODO send not modified for non scrips and css
    file.http = {
        'Vary': 'Accept-Encoding',
        'Cache-Control': 'public, max-age=' + (file.maxAge || engine.http.maxAge),
        'Content-Length': file.data.length,
        'Content-Type': file.type,
        'Etag': file.fp
    };

    // set last modified header
    if (file.mtime) {
        file.http['Last-Modified'] = file.mtime;
    }

    // send encoding header
    if (!file.noCompression) {
        file.http['Content-Encoding'] = 'gzip';
    }

    // save zipped data in cache
    fileCache.set(file.read, file);

    // return data
    callback(null, file, context);
}

/**
 * Remove fingerprint from path
 *
 * @private
 * @param {string} The file path.
 */
function removeFingerprint (path) {

    // remove fingerprint if it's found in path
    if (checkFp.test(path)) {

        // split path
        path = path.split('.');

        // remove fingerprint from read path
        path.splice(path.length - 2, 1);

        // create path without fingerprint
        path = path.join('.');
    }

    // return path
    return path;
}
<|endoftext|>


<commit_before>var path = require('path')
  , fs = require('fs')
  , clone = require('clone')
  , data = require('./data')
  , getset = require('deep-get-set')
  , DeepMerge = require('deep-merge')
  , merge = DeepMerge(function(a, b) {
      return b
    });

getset.p = true;

function File(path) {
  this._path = path;
  this._obj = {};
}

File.prototype.open = function() {
  if (fs.existsSync(this._path)) {
    var text = fs.readFileSync(this._path, 'utf8')
      , format = data.createFormat(path.extname(this._path));

    this._obj = format.parse(text);
  }
}

File.prototype.get = function(path) {
  path = path.replace(/\//g, '.');
  return clone(getset(this._obj, path));
}

File.prototype.toObject = function() {
  return clone(this._obj);
}

module.exports = File;
<commit_msg>Remove unused deep-merge dependency from File.<commit_after>var path = require('path')
  , fs = require('fs')
  , clone = require('clone')
  , getset = require('deep-get-set')
  , data = require('./data');

getset.p = true;

function File(path) {
  this._path = path;
  this._obj = {};
}

File.prototype.open = function() {
  if (fs.existsSync(this._path)) {
    var text = fs.readFileSync(this._path, 'utf8')
      , format = data.createFormat(path.extname(this._path));

    this._obj = format.parse(text);
  }
}

File.prototype.get = function(path) {
  path = path.replace(/\//g, '.');
  return clone(getset(this._obj, path));
}

File.prototype.toObject = function() {
  return clone(this._obj);
}

module.exports = File;
<|endoftext|>


<commit_before>var fs = require('fs');
var zlib = require('zlib');
var mime = require('mime');
var minifyHtml = require('html-minifier').minify;
var minifyCSS =  require('clean-css');
var UglifyJS = require('uglify-js');
var Fingerprint = require('./fingerprint');

// get event emitter module
var EventEmitter = require('events').EventEmitter;

var html_minifier_options = {
    removeComments: true,
    removeCommentsFromCDATA: true,
    removeCDATASectionsFromCDATA: true,
    removeAttributeQuotes: true,
    removeRedundantAttributes: true,
    removeEmptyAttributes: true,
    removeOptionalTags: true,
    collapseBooleanAttributes: true,
    collapseWhitespace: true
};

// remove fingerprint from script files
var reRemoveFingerprint = /\.[^\.]+\.js$/;

// save caches
var caches = {};

// this is not quaranteed to be stable! why?
zlib.Z_DEFAULT_COMPRESSION = 9;

// export the file cache factory
module.exports = factory;

/**
 * The file cache factory.
 *
 * @public
 * @param {string} The unique file cache name.
 * @param {object} Options for the file cache.
 */
function factory (name, options) {

    // return existing cache
    if (caches[name]) {
        return caches[name];
    }

    // clone cache class
    var cache = engine.clone(Cache);

    // set observer
    cache.obs = new EventEmitter();

    // create data container
    cache._data = {};

    // working dir
    cache._wd = options.wd || engine.paths.app_public;

    // dont wrap js code
    cache._noWrap = options.noWrap;

    // configure compression
    cache._noCompression = options.noCompression;

    // dont optimize html and css
    cache._dontOptimize = options.dontOptimize;

    // save cache
    caches[name] = cache;

    return cache;
}

/**
 * The file cache class.
 *
 * @class Cache
 */
var Cache = {

    /**
     * Remove a file from the cache. Or with no argument empty the cache.
     *
     * @public
     * @param {string} The file path.
     */
    rm: function (path) {

        // remove one item
        if (path) {

            delete this._data[path];

            // emit item remove event
            this.obs.emit('remove:' + path);

            return;
        }

        // remove all items
        this._data = {};
    },

    /**
     * Get a file, or get all files in the cache.
     *
     * @public
     * @param {string} The file path.
     */
    get: function (path) {

        // return one item
        if (path) {
            return this._data[path];
        }

        // return all items
        return this._data;
    },

    /**
     * Save an file in the cache.
     *
     * @public
     * @param {string} The file path.
     * @param {function} The callback function.
     * @param {array} Give an array with allowed paths. (optional)
     * @param {string} A string, which is prepended to a path.
     */
    set: function (path, callback, allowedFiles, prependPath) {
        var self = this;

        // clean path and get figerprint info
        var pathInfo = Fingerprint.cleanPath(path);

        // create a clean path without the fingerprint
        var cleanPath = pathInfo.path;

        // check if the file is allowed
        if (allowedFiles) {

            var found = false;

            // try to find the file
            for (var i = 0; i < allowedFiles.length; ++i) {
                if (allowedFiles[i].replace(/^\.\//, '') === path) {
                    found = true;
                    break;
                }
            }

            // return not found if requested file is not found in "allowedFiles"
            if (!found) {
                var err = new Error('File not found.');
                console.error('lib/file.js#158', err.toString());
                return callback(err);
            }
        }

        // path must be absolute
        if (cleanPath.indexOf('../') !== -1) {
            var err = new Error('Invalid path.');
            console.error('lib/file.js#166', err.toString());
            return callback(err);
        }

        // check if file is already in cache
        if (self._data[cleanPath]) {
            return callback(null, self._data[cleanPath], true);
        }

        var mimeType = mime.lookup(cleanPath);

        // TODO this looks a little whacky.. find a better way to distinct core client and module client files
        var readPath = self._wd + (allowedFiles ? (prependPath || '') : '') + (cleanPath[0] === '/' ? cleanPath.substr(1) : cleanPath);

        fs.readFile(readPath, function (err, data) {

            if (err) {
                console.error('lib/file.js#183', err.toString());
                return callback(err);
            }

            // minify js, html or css
            if (!self._dontOptimize) {
                switch (mimeType) {
                    case 'text/html':
                        //data = minifyHtml(data.toString(), html_minifier_options);
                        break;
                    case 'text/css':
                        data = new minifyCSS({processImport: false}).minify(data.toString()).styles;
                        break;
                    case 'application/javascript':

                        // wrap javascript files, except the engine client
                        if (cleanPath !== 'engine.js' && !self._noWrap) {
                            data = "E('" + (prependPath || '') + cleanPath + "',function(require,module,exports){\n" + data.toString() + "\nreturn module});";
                        }

                        if (engine.production && (data = UglifyJS.minify(data.toString(), {fromString: true}))) {
                            data = data.code;
                        }
                        break;
                }
            }

            // don't compress data
            if (self._noCompression) {
                return createCacheObject.call(self, cleanPath, readPath, data, mimeType, pathInfo.fingerprint, callback);
            }

            zlib.gzip(data, function (err, data) {

                if (err) {
                    console.error('lib/file.js#218', err.toString());
                    return callback(err);
                }

                createCacheObject.call(self, cleanPath, readPath, data, mimeType, pathInfo.fingerprint, callback);
            });
        });
    }
};

/**
 * Create an cache object with the data buffer and the http headers.
 *
 * @private
 * @param {string} The file name without the fingerprint.
 * @param {string} The file path wihtout the fingerprint.
 * @param {blob} The file data.
 * @param {string} The mime type.
 * @param {string} The fingerprint.
 * @param {function} The callback function.
 */
function createCacheObject (cleanPath, readPath, data, mimeType, fingerprint, callback) {
    var self = this;

    fs.stat(readPath, function (err, stats) {

        if (err) {
            console.error('lib/file.js#245', err.toString());
            return callback(err);
        }

        // watch for file changes
        if (self._data[cleanPath] !== null) {
            fs.watch(readPath, function (event, filename) {

                // remove cached data on file change, to force reload
                if (event === 'change' && self._data[cleanPath]) {
                    self._data[cleanPath] = null;

                    // emit item change event
                    self.obs.emit('change:' + cleanPath);
                }
            });
        }

        data = {
            data: data,

            // create http headers
            // TODO send not modified for non scrips and css
            http: {
                'Vary': 'Accept-Encoding',
                'Cache-Control': 'public, max-age=' + (fingerprint ? engine.http.maxAge_fingerprint : engine.http.maxAge),
                'Content-Encoding': 'gzip',
                'Content-Length': data.length,
                'Content-Type': mimeType
            }
        };

        // handle files without fingerprint
        if (!fingerprint) {
            data.http.Etag = stats.mtime.getTime().toString(36).slice(-7);
            data.http['Last-Modified'] = stats.mtime;
        }

        // know if item was removed due a composition change or manual
        var changed = self._data[cleanPath] === null ? true : false;

        // save zipped data in cache
        self._data[cleanPath] = data;

        // return data
        callback(null, data, changed);
    });
}
<commit_msg>handle fingerprints in the file cache<commit_after>/**
 * The file cache class.
 *
 * @class Cache
 */
var fs = require('fs');
var zlib = require('zlib');
var mime = require('mime');
var crypto = require('crypto');
var minifyHtml = require('html-minifier').minify;
var minifyCSS =  require('clean-css');
var UglifyJS = require('uglify-js');

var html_minifier_options = {
    removeComments: true,
    removeCommentsFromCDATA: true,
    removeCDATASectionsFromCDATA: true,
    removeAttributeQuotes: true,
    removeRedundantAttributes: true,
    removeEmptyAttributes: true,
    removeOptionalTags: true,
    collapseBooleanAttributes: true,
    collapseWhitespace: true
};

// regular expressions
//var removeFingerprint = /\.[^\.]+\.js$/;
var suffix = /\.\w+$/;
var checkFp = /\.@[a-z0-9]{7}\.(?:js|css)$/i;

// global file cache
var fileCache = engine.pojo('files');

// this is not quaranteed to be stable! why?
zlib.Z_DEFAULT_COMPRESSION = 9;

/**
 * Remove a file from the cache. Or with no argument empty the cache.
 *
 * @public
 * @param {string} The file path.
 */
exports.rm = function (path) {

    // remove one item
    if (path) {
        return fileCache.rm(path);
    }
},

/**
 * Read a file or get it from the cache.
 *
 * @public
 * @param {string} The file path.
 * @param {function} The callback function.
 * @param {array} Give an array with allowed paths. (optional)
 * @param {string} A string, which is prepended to a path.
 */
exports.get = getFile;
function getFile (parentDir, path, callback, options) {

    // path must be absolute
    if (path.indexOf('../') !== -1) {
        var err = new Error('Invalid path.');
        console.error('lib/file.js#80', err.toString());
        return callback(err);
    }

    options = options || {};

    // set default working dir to public app folder
    parentDir = parentDir || engine.paths.app_public;

    // check if the file is allowed
    if (options.allowedFiles) {

        var found = false;

        // try to find the file
        for (var i = 0; i < options.allowedFiles.length; ++i) {
            if (options.allowedFiles[i].replace(/^\.\//, '') === path) {
                found = true;
                break;
            }
        }

        // return not found if requested file is not found in "allowedFiles"
        if (!found) {
            var err = new Error('File not found.');
            console.error('lib/file.js#158', err.toString());
            return callback(err);
        }
    }

    // create a clean path without the fingerprint
    var cleanPath = removeFingerprint(path);

    // check if path had fingerprint
    var hasFingerprint = cleanPath === path ? false : true;

    // check if file is already in cache
    var file = fileCache.get(cleanPath);

    if (file) {
        return callback(null, file, true);
    }

    // TODO this looks a little whacky.. find a better way to distinct core client and module client files
    var readPath = parentDir + (options.allowedFiles ? (options.prependPath || '') : '') + (cleanPath[0] === '/' ? cleanPath.substr(1) : cleanPath);

    readFile(file, parentDir, readPath, cleanPath, options, hasFingerprint, callback);
};

function readFile (file, parentDir, path, cleanPath, options, hasFingerprint, callback) {

    var mimeType = mime.lookup(path);
    fs.readFile(path, function (err, data) {

        if (err) {
            console.error('lib/file.js#183', err.toString());
            return callback(err);
        }

        // minify js, html or css
        switch (mimeType) {
            case 'text/html':
                //data = minifyHtml(data.toString(), html_minifier_options);
                break;
            case 'text/css':
                data = new minifyCSS({processImport: false}).minify(data.toString()).styles;
                break;
            case 'application/javascript':

                // wrap javascript files, except the engine client
                if (cleanPath !== 'engine.js') {
                    data = "E('" + (options.prependPath || '') + cleanPath + "',function(require,module,exports){\n" + data.toString() + "\nreturn module});";
                }

                if (engine.production && (data = UglifyJS.minify(data.toString(), {fromString: true}))) {
                    data = data.code;
                }
                break;
        }

        // create fingerprint
        var fingerprint = crypto.createHash('md5').update(data).digest('hex').substr(7);

        // don't compress data
        if (options.noCompression) {
            return createCacheObject(file, cleanPath, path, data, mimeType, fingerprint, hasFingerprint, callback);
        }

        zlib.gzip(data, function (err, data) {

            if (err) {
                console.error('lib/file.js#218', err.toString());
                return callback(err);
            }

            createCacheObject(file, cleanPath, path, data, mimeType, fingerprint, hasFingerprint, callback);
        });
    });
}

/**
 * Create an cache object with the data buffer and the http headers.
 *
 * @private
 * @param {string} The file name without the fingerprint.
 * @param {string} The file path wihtout the fingerprint.
 * @param {blob} The file data.
 * @param {string} The mime type.
 * @param {string} The fingerprint.
 * @param {function} The callback function.
 */
function createCacheObject (file, cleanPath, readPath, data, mimeType, fingerprint, hasFingerprint, callback) {

    fs.stat(readPath, function (err, stats) {

        if (err) {
            console.error('lib/file.js#245', err.toString());
            return callback(err);
        }

        // watch for file changes
        if (file !== null) {
            fs.watch(readPath, function (event, filename) {

                // remove cached data on file change, to force reload
                if (event === 'change') {

                    // set file item to null
                    fileCache.set(readPath, null);
                }
            });
        }

        data = {
            data: data,

            // create http headers
            // TODO send not modified for non scrips and css
            http: {
                'Vary': 'Accept-Encoding',
                'Cache-Control': 'public, max-age=' + (hasFingerprint ? engine.http.maxAge_fingerprint : engine.http.maxAge),
                'Content-Encoding': 'gzip',
                'Content-Length': data.length,
                'Content-Type': mimeType,
                'Last-Modified': stats.mtime,
                'Etag': fingerprint
            }
        };

        // know if item was removed due a composition change or manual
        var changed = file === null ? true : false;

        // save zipped data in cache
        fileCache.set(cleanPath, data);

        // return data
        callback(null, data, changed);
    });
}

/**
 * Extend file name with a content hash
 *
 * @public
 * @param {string} The module name, which contains the files.
 * @param {array} The file paths.
 * @param {object} The callback function.
 * @param {boolean} Attach the fingerprint as a query parameter.
 */
exports.addFingerprints = function (module, files, callback, query) {

    if (!files) {
        var err = new Error('No file informations.');
        console.error('lib/fingerprint.js#22', err.toString());
        return callback(err);
    }

    if (files.length === 0) {
        return callback();
    }

    // recursive handler
    var handler = function (index) {

        // call back when all file path are updated
        if (!files[index]) {
            return callback(null, files);
        }

        // check if file points to an external source
        if (files[index].indexOf('//') > -1) {
            return handler(++index);
        }

        var file;
        var cwd;
        var options = {};

        // get working directory and file path
        switch (files[index][0]) {
            case '.':
                cwd = engine.paths.app_modules + module + '/';
                file = files[index].substr(2);
                options.prependPath = module + '/';
                break;
            case '/':
                cwd = engine.paths.app_public;
                file = files[index].substr(1);
                break;
            default:
                cwd = engine.root + 'lib/client/';
                file = files[index];
                options.prependPath = 'E/';
        }

        // get the modification time
        // TODO create hash from file content (MD5)
        getFile(cwd, file,
            function (err, data) {

                // handle error
                if (err) {
                    console.error('lib/fingerprint.js#69', err.toString());
                    return callback(err);
                }

                // make fingerprint more unique
                stats = '@' + data.http.Etag.substr(0, 7);

                // handle files without suffix
                if (!suffix.test(file)) {

                    // update files
                    files[index] += '.' + stats;

                    // next loop
                    return handler(++index);
                }

                // append commit id as query
                if (query) {
                    files[index] += '?' + stats;

                    // next loop
                    return handler(++index);
                }

                // handle files with suffix
                file = files[index].split('.');
                file.splice(file.length - 1, 0, stats);

                // update files
                files[index] = file.join('.');

                // next loop
                handler(++index);
            },
            options
        );
    };

    // start recursive loop
    handler(0);
}

/**f
 * Remove fingerprint from path
 *
 * @private
 * @param {string} The file path.
 */
function removeFingerprint (path) {

    // remove fingerprint if it's found in path
    if (checkFp.test(path)) {

        // split path
        path = path.split('.');

        // get fingerprint and remove fingerprint from read path
        path.splice(path.length - 2, 1)[0];

        // create path without fingerprint
        path = path.join('.');
    }

    // return path
    return path;
}
<|endoftext|>