<commit_before>#! /usr/bin/env node
/*
 * redirect-rest
 * https://github.com/juanghurtado/redirect-rest
 *
 * Copyright (c) 2014 Juan G. Hurtado
 * Licensed under the MIT license.
 */

'use strict';

var express = require("express");
var bodyParser = require("body-parser");
var path = require("path");
var request = require("request");
var _ = require("underscore");
var fs = require("fs");
var configfile = ".redirect-rest.json";

fs.readFile(configfile, "utf8", function (err, data) {
  if (err) {
    console.log("Can't find a .redirect-rest.json file. Please create it to tell us what requests you want to redirect to a remote server.");
    console.log("Error: " + err);
    return;
  }

  var options = JSON.parse(data);

  if (!options.remote_url || options.remote_url == "") {
    console.log("You must set a value for `remote_url` attribute on .redirect-rest.json file");
    return;
  }

  if (!options.routes) {
    console.log("You must set an array of values for `routes` attribute on .redirect-rest.json file");
    return;
  }

  var html_extensions = options.html_extensions || ["jsp", "php"];
  express.static.mime.define({'text/html': html_extensions});

  var app = express();
  app.use(bodyParser());
  app.use(express.static(options.public_path || "./"));

  function request_server(local_request, local_response, method) {
    console.log("Type: "+ method);
    console.log("From: http://localhost:4242"+ local_request.url);
    console.log("To:   "+ options.remote_url + local_request.url);
    console.log("----------------------------------------");

    request({
      url: (options.remote_url + local_request.url),
      json: local_request.body,
      method: method
    }, function(error, remote_response, body) {
      if (error) {
        console.log("Error requesting remote server: "+ error);
      } else {
        local_response.json(remote_response.body);
      }
    });
  };

  _.each(options.routes, function(route) {
    // GET: List
    app.get("/"+ route, function(req, res) {
      request_server(req, res, "GET");
    });

    // POST: Create
    app.post("/"+ route, function(req, res) {
      request_server(req, res, "POST");
    });

    // GET: Get
    app.get("/"+ route +"/:id", function(req, res) {
      request_server(req, res, "GET");
    });

    // POST: Update
    app.post("/"+ route +"/:id", function(req, res) {
      request_server(req, res, "POST");
    });

    // DELETE: Delete
    app.delete("/"+ route +"/:id", function(req, res) {
      request_server(req, res, "POST");
    });
  });

  // Launch server
  app.listen(4242);
  console.log("Server running at http://localhost:4242/");
});
<commit_msg>Fix DELETE redirect<commit_after>#! /usr/bin/env node
/*
 * redirect-rest
 * https://github.com/juanghurtado/redirect-rest
 *
 * Copyright (c) 2014 Juan G. Hurtado
 * Licensed under the MIT license.
 */

'use strict';

var express = require("express");
var bodyParser = require("body-parser");
var path = require("path");
var request = require("request");
var _ = require("underscore");
var fs = require("fs");
var configfile = ".redirect-rest.json";

fs.readFile(configfile, "utf8", function (err, data) {
  if (err) {
    console.log("Can't find a .redirect-rest.json file. Please create it to tell us what requests you want to redirect to a remote server.");
    console.log("Error: " + err);
    return;
  }

  var options = JSON.parse(data);

  if (!options.remote_url || options.remote_url == "") {
    console.log("You must set a value for `remote_url` attribute on .redirect-rest.json file");
    return;
  }

  if (!options.routes) {
    console.log("You must set an array of values for `routes` attribute on .redirect-rest.json file");
    return;
  }

  var html_extensions = options.html_extensions || ["jsp", "php"];
  express.static.mime.define({'text/html': html_extensions});

  var app = express();
  app.use(bodyParser());
  app.use(express.static(options.public_path || "./"));

  function request_server(local_request, local_response, method) {
    console.log("Type: "+ method);
    console.log("From: http://localhost:4242"+ local_request.url);
    console.log("To:   "+ options.remote_url + local_request.url);
    console.log("----------------------------------------");

    request({
      url: (options.remote_url + local_request.url),
      json: local_request.body,
      method: method
    }, function(error, remote_response, body) {
      if (error) {
        console.log("Error requesting remote server: "+ error);
      } else {
        local_response.json(remote_response.body);
      }
    });
  };

  _.each(options.routes, function(route) {
    // GET: List
    app.get("/"+ route, function(req, res) {
      request_server(req, res, "GET");
    });

    // POST: Create
    app.post("/"+ route, function(req, res) {
      request_server(req, res, "POST");
    });

    // GET: Get
    app.get("/"+ route +"/:id", function(req, res) {
      request_server(req, res, "GET");
    });

    // POST: Update
    app.post("/"+ route +"/:id", function(req, res) {
      request_server(req, res, "POST");
    });

    // DELETE: Delete
    app.delete("/"+ route +"/:id", function(req, res) {
      request_server(req, res, "DELETE");
    });
  });

  // Launch server
  app.listen(4242);
  console.log("Server running at http://localhost:4242/");
});
<|endoftext|>


<commit_before>var http = require('http')
  , urlParse = require('url').parse
  , debug = require('debug')('spdy-referrer-push')
  , expressRequest = require('express/lib/request')
  , expressResponse = require('express/lib/response');

var pushRegexps = [
  /.*\.css/,
  /.*\.js/,
  /.*\.png/,
  /.*\.jpeg/,
  /.*\.jpg/,
  /.*\.gif/,
  /.*\.ico/,
  /.*\.woff/
];
var pushContentTypes = [
  "text/css",
  "text/javascript",
  "application/javascript",
  "application/x-javascript",
  "image/png",
  "image/x-png",
  "image/jpeg",
  "image/gif",
  "image/x-icon",
  "image/vnd.microsoft.icon"
];
var maxAssociatedResources = 32;
var referrerPushPeriod = 5000; // millis
var mainResources = {};

var isPushResource = function(url) {
  var result = false;
  pushRegexps.forEach(function (pushRegexp) {
    if (pushRegexp.test(url)) {
      result = true;
    }
  });
  return result;
};

var isMainResource = function(url) {
  return !isPushResource(url);
}

var getOrCreateMainResource = function(url) {
  if (!mainResources[url]) {
    var firstPushResourceAdded = -1;
    var pushResources = [];
    mainResources[url] = {
      name: url,
      firstPushResourceAdded: firstPushResourceAdded,
      getPushResources: function() {
        return pushResources.slice(0);
      },
      addPushResource: function(pushResourceUrl, host, referrer) {
        if (firstPushResourceAdded === -1) {
          firstPushResourceAdded = Date.now();
        }
        var delay = Date.now() - firstPushResourceAdded;
        if (urlParse(referrer).host !== host) {
          debug("Skipped store of push metadata " + pushResourceUrl + " for main resource " + url + ": Hostname: " + host + " doesn't match referrer " + referrer);
          return false;
        }
        if (pushResources.length >= maxAssociatedResources) {
          debug("Skipped store of push metadata " + pushResourceUrl + " for main resource " + url + ": Max associated resources (" + maxAssociatedResources + ") reached");
          return false;
        }
        if (delay > referrerPushPeriod) {
          debug("Skipped store of push metadata " + pushResourceUrl + " for main resource " + url + ": Delay " + delay + "ms longer than referrerPushPeriod (" + referrerPushPeriod + "ms)");
          return false;
        }
        debug("Adding: " + pushResourceUrl + " to: " + url + " with delay: " + delay + "ms.");
        pushResources.push(pushResourceUrl);
        debug("Push resources for " + url + " are now " + pushResources);
        return true;
      }
    };
  }
  return mainResources[url];
};

var getContentType = function(url) {
  if (/.*\.css/.test(url)) {
    return 'text/css';
  }
  if (/.*\.js/.test(url)) {
    return 'application/javascript';
  }
  if (/.*\.png/.test(url)) {
    return 'image/png';
  }
  if (/.*\.jpe?g/.test(url)) {
    return 'image/jpeg';
  }
  if (/.*\.gif/.test(url)) {
    return 'image/gif';
  }
  if (/.*\.ico/.test(url)) {
    return 'image/x-icon';
  }
  if (/.*\.html?/.test(url)) {
    return 'text/html';
  }
  if (/.*\.json/.test(url)) {
    return 'application/json';
  }
  if (/.*\.txt/.test(url)) {
    return 'text/plain';
  }
  if (/.*\.woff/.test(url)) {
    return 'application/octet-stream';
  }
  return '';
};

var push = function(req, res, url) {
  debug("Server push " + url);
  var headers = { 'content-type': getContentType(url) };
  var internalRequest = { __proto__: expressRequest, app: req.app };
  internalRequest.socket = { remoteAddress: '127.0.0.1' };  // for logging middleware
  internalRequest.method = 'GET';
  internalRequest.url = url;
  internalRequest.params = {};
  internalRequest.headers = {};
  internalRequest.body = {};
  internalRequest.query = {};
  internalRequest.files = {};
  internalRequest.isInternalRequest = true;
  var internalResponse = { __proto__: expressResponse, app: res.app };
//  var internalResponse = new http.ServerResponse(internalRequest);
//  internalResponse.__proto__ = expressResponse;
//  internalResponse.app = res.app;
  internalResponse.cookies = {};
  var stream = res.push(url, headers);
  internalResponse.write = function(data) {
    stream.write(data);
  };
  internalResponse.end = function(data) {
    stream.end(data);
  };
//  internalResponse.pipe(stream);
  req.app.handle(internalRequest, internalResponse, function(error) {
    debug("Error when pushing resource [" + url + "]: " + error.message);
  });
};

exports.referrer = function() {
  return function(req, res, next) {
    if (req.isInternalRequest) {
      return next();
    }
    if (!res.push && typeof(a) !== 'function') {
      debug("Not handling SPDY server push for URL " + req.url + " because req.push does not exist or is not a function");
      return next();
    }
    if (req.method !== 'GET') {
      debug("Not handling SPDY server push for URL " + req.url + " because the HTTP method is " + req.method);
      return next();
    }
    if (req.headers['if-modified-since']) {
      debug("Not handling SPDY server push for URL " + req.url + " because If-Modified-Since header is present");
      return next();
    }
    debug("Handling server push for " + req.url);
    if (isMainResource(req.url)) {
      debug("URL [" + req.url + "] is a main resource for SPDY server push");
      var mainResource = getOrCreateMainResource(req.url);
      var pushResources = mainResource.getPushResources();
      debug("Pushing resources for URL [" + req.url + "]: " + pushResources);
      pushResources.forEach(function (pushResourceUrl) {
        push(req, res, pushResourceUrl);
      });
    } else if (isPushResource(req.url)) {
      var referrer = req.headers['referer'];
      debug("URL [" + req.url + "] is a push resource for SPDY server push with referrer " + referrer);
      if (referrer) {
        var mainResource = getOrCreateMainResource(urlParse(referrer).path);
        var pushResources = mainResource.getPushResources();
        if (pushResources.indexOf(req.url) === -1) {
          debug("Push resource " + req.url + " not already added for main resource " + referrer);
          mainResource.addPushResource(req.url, req.headers['host'], referrer);
        } else {
          debug("Push resource " + req.url + " already added for main resource " + referrer + ", now pushing push resources for " + req.url);
          getOrCreateMainResource(req.url).getPushResources().forEach(function (pushResourceUrl) {
            push(req, res, pushResourceUrl);
          });
        }
      }
    }

    return next();
  };
};

<commit_msg>Minor code cleanup<commit_after>var http = require('http')
  , urlParse = require('url').parse
  , debug = require('debug')('spdy-referrer-push')
  , expressRequest = require('express/lib/request')
  , expressResponse = require('express/lib/response');

var pushRegexps = [
  /.*\.css/,
  /.*\.js/,
  /.*\.png/,
  /.*\.jpeg/,
  /.*\.jpg/,
  /.*\.gif/,
  /.*\.ico/,
  /.*\.woff/
];
var maxAssociatedResources = 32;
var referrerPushPeriod = 5000; // millis
var mainResources = {};

var isPushResource = function(url) {
  var result = false;
  pushRegexps.forEach(function (pushRegexp) {
    if (pushRegexp.test(url)) {
      result = true;
    }
  });
  return result;
};

var getOrCreateMainResource = function(url) {
  if (!mainResources[url]) {
    var firstPushResourceAdded = -1;
    var pushResources = [];
    mainResources[url] = {
      name: url,
      firstPushResourceAdded: firstPushResourceAdded,
      getPushResources: function() {
        return pushResources.slice(0);
      },
      addPushResource: function(pushResourceUrl, host, referrer) {
        if (firstPushResourceAdded === -1) {
          firstPushResourceAdded = Date.now();
        }
        var delay = Date.now() - firstPushResourceAdded;
        if (urlParse(referrer).host !== host) {
          debug("Skipped store of push metadata " + pushResourceUrl + " for main resource " + url + ": Hostname: " + host + " doesn't match referrer " + referrer);
          return false;
        }
        if (pushResources.length >= maxAssociatedResources) {
          debug("Skipped store of push metadata " + pushResourceUrl + " for main resource " + url + ": Max associated resources (" + maxAssociatedResources + ") reached");
          return false;
        }
        if (delay > referrerPushPeriod) {
          debug("Skipped store of push metadata " + pushResourceUrl + " for main resource " + url + ": Delay " + delay + "ms longer than referrerPushPeriod (" + referrerPushPeriod + "ms)");
          return false;
        }
        debug("Adding: " + pushResourceUrl + " to: " + url + " with delay: " + delay + "ms.");
        pushResources.push(pushResourceUrl);
        debug("Push resources for " + url + " are now " + pushResources);
        return true;
      }
    };
  }
  return mainResources[url];
};

var getContentType = function(url) {
  if (/.*\.css/.test(url)) {
    return 'text/css';
  }
  if (/.*\.js/.test(url)) {
    return 'application/javascript';
  }
  if (/.*\.png/.test(url)) {
    return 'image/png';
  }
  if (/.*\.jpe?g/.test(url)) {
    return 'image/jpeg';
  }
  if (/.*\.gif/.test(url)) {
    return 'image/gif';
  }
  if (/.*\.ico/.test(url)) {
    return 'image/x-icon';
  }
  if (/.*\.html?/.test(url)) {
    return 'text/html';
  }
  if (/.*\.json/.test(url)) {
    return 'application/json';
  }
  if (/.*\.txt/.test(url)) {
    return 'text/plain';
  }
  if (/.*\.woff/.test(url)) {
    return 'application/octet-stream';
  }
  return '';
};

var push = function(req, res, url) {
  debug("Server push " + url);
  var internalRequest = {
    __proto__: expressRequest,
    app: req.app,
    socket: { remoteAddress: '127.0.0.1' },  // for logging middleware
    method: 'GET',
    url: url,
    params: {},
    headers: {},
    body: {},
    query: {},
    files: {},
    isInternalRequest: true
  };
  var internalResponse = {
    __proto__: expressResponse,
    app: res.app,
    cookies: {}
  };
  var stream = res.push(url, { 'content-type': getContentType(url) });
  internalResponse.write = function(data) {
    stream.write(data);
  };
  internalResponse.end = function(data) {
    stream.end(data);
  };
  req.app.handle(internalRequest, internalResponse, function(error) {
    debug("Error when pushing resource [" + url + "]: " + error.message);
  });
};

var handleMainResourceType = function (req, res) {
  debug("URL [" + req.url + "] is a main resource for SPDY server push");
  var mainResource = getOrCreateMainResource(req.url);
  var pushResources = mainResource.getPushResources();
  debug("Pushing resources for URL [" + req.url + "]: " + pushResources);
  pushResources.forEach(function (pushResourceUrl) {
    push(req, res, pushResourceUrl);
  });
};

var handlePushResourceType = function (req, res) {
  var referrer = req.headers['referer'];
  debug("URL [" + req.url + "] is a push resource for SPDY server push with referrer " + referrer);
  if (referrer) {
    var mainResource = getOrCreateMainResource(urlParse(referrer).path);
    var pushResources = mainResource.getPushResources();
    if (pushResources.indexOf(req.url) === -1) {
      debug("Push resource " + req.url + " not already added for main resource " + referrer);
      mainResource.addPushResource(req.url, req.headers['host'], referrer);
    } else {
      debug("Push resource " + req.url + " already added for main resource " + referrer + ", now pushing push resources for " + req.url);
      getOrCreateMainResource(req.url).getPushResources().forEach(function (pushResourceUrl) {
        push(req, res, pushResourceUrl);
      });
    }
  }
};

var shouldHandleRequest = function(req, res) {
  if (req.isInternalRequest) {
    return false;
  }
  if (!res.push && typeof(a) !== 'function') {
    debug("Not handling SPDY server push for URL " + req.url + " because req.push does not exist or is not a function");
    return false;
  }
  if (req.method !== 'GET') {
    debug("Not handling SPDY server push for URL " + req.url + " because the HTTP method is " + req.method);
    return false;
  }
  if (req.headers['if-modified-since']) {
    debug("Not handling SPDY server push for URL " + req.url + " because If-Modified-Since header is present");
    return false;
  }
  return true;
};

exports.referrer = function () {
  return function (req, res, next) {
    if (shouldHandleRequest(req, res)) {
      debug("Handling server push for " + req.url);
      if (!isPushResource(req.url)) {
        handleMainResourceType(req, res);
      } else {
        handlePushResourceType(req, res);
      }
    }
    return next();
  };
};
<|endoftext|>


<commit_before>'use strict';

var path = require('path');
var http = require('http');
var https = require('https');
var url = require('url');

var _ = require('lodash');
var fs = require('graceful-fs');
var gitlab = require('gitlab');
var Promise = require('bluebird');
var semver = require('semver');
var ProgressBar = require('progress');

var fecom = require('../fecom');

var GitlabRepo = function () {
  this.gitlab = {};
  this.repo = null;
};

GitlabRepo.prototype = {
  constructor: GitlabRepo,
  initialize: function () {
    var self = this;
    self.gitlab.token = fecom.profile.token;
    self.gitlab = gitlab({
      token: fecom.profile.token,
      url: fecom.config.domain + '/api/v3'
    });
    self.repo = self.gitlab.projects.repository;
  },
  getArchive: function (namespace, projectName, tagName) {
    var archiveUrl = fecom.config.domain + '/' + namespace + '/' + projectName + '/repository/archive.zip?ref=' + tagName + '&private_token=' + fecom.profile.token;
    var prefix = namespace + '/' + projectName + '-' + tagName;
    var archivePath = path.join(fecom.tmpDir, prefix + '-archive.zip');
    archiveUrl = url.parse(archiveUrl);
    var client = ('http:' === archiveUrl.protocol ? http : https);
    var semantic = fecom.stringify({
      name: projectName,
      owner: namespace,
      version: tagName
    });

    return new Promise(function (resolve) {
      // fecom.logger.info('Begin to download ' + specified);
      // fecom.logger.info(archiveUrl.href);
      var req = client.request(archiveUrl);
      req.end();
      req.on('response', function (res) {
        var dirname = path.join(fecom.tmpDir, namespace);
        var contentLength = res.headers['content-length'] >> 0;
        var bar = new ProgressBar(fecom.i18n('DOWNLOADING_COMPONENT', semantic), {
          complete: '=',
          incomplete: ' ',
          width: 20,
          total: contentLength
        });

        if (!fs.existsSync(dirname)) {
          fs.mkdirSync(dirname);
        }

        var stream = fs.createWriteStream(archivePath);
        res.on('data', function (chunk) {
          bar.tick(chunk.length);
          stream.write(chunk);
        });
        res.on('end', function () {
          stream.on('finish', function () {
            // fecom.logger.info('Finish downloading ' + prefix);
            resolve(archivePath);
          });
          stream.end();
        });
      });
    });

  },
  validate: function (namespace, projectName, version) {
    var self = this;
    var latestTag = '';
    var promise = version ? fecom.async(version) : self.getLatestTag(namespace, projectName).then(function (tag) { return tag.name; });

    return promise
      .then(function (tagName) {
        latestTag = tagName;
        return self.getComponentJson(namespace, projectName, tagName);
      })
      .then(function (json) {

        if (json.version !== latestTag) {
          throw new Error(fecom.i18n('ERROR_INVALID_COMPONENT', projectName));
        }

        return json;
      });
  },
  getDependencies: function (namespace, projectName, version) {
    // Get dependencies
    var self = this;
    var node = {};

    return self.validate(namespace, projectName, version)
      .then(function (json) {
        json.dependencies = json.dependencies || [];
        version = version || json.version;
        node.name = fecom.stringify({
          name: projectName,
          owner: namespace,
          version: version
        });
        node.dependencies = json.dependencies.map(function (specified) {
          return fecom.stringify(fecom.parse(specified));
        });

        return node;
      });
  },
  getAllTags: function (namespace, projectName) {
    var self = this;
    var projectId = namespace + '/' + projectName;
    return new Promise(function (resolve, reject) {
      self.repo.listTags(projectId, function (tags) {
        if (!tags) {
          reject(new Error('Repository not found'));
          return false;
        }

        if (tags && !tags.length) {
          reject(new Error('Component tags not found in ' + projectId));
          return false;
        }

        resolve(tags);
      });
    });
  },
  getLatestTag: function (namespace, projectName) {
    var self = this;
    return self.getAllTags(namespace, projectName)
      .then(function (tags) {
        var latest = {};
        latest = _(tags).sortBy('commit.committed_date').last();
        return latest;
      });
  },
  getComponentJson: function (namespace, projectName, tagName) {
    var self = this;
    return new Promise(function (resolve, reject) {
      var projectId = namespace + '/' + projectName;
      self.repo.showFile({
        projectId: projectId,
        ref: 'tags/' + tagName,
        file_path: 'component.json'
      }, function (file) {
        var content, json;

        if (!file) {
          reject(new Error('"component.json" not found in ' + projectId));
          return false;
        }

        content = (new Buffer(file.content, 'base64')).toString();

        try {
          json = JSON.parse(content);
        } catch (err) {
          reject(err);
          return false;
        }

        resolve(json);
      });
    });
  },
  searchComponents: function (pattern, options) {
    var self = this;

    return new Promise(function (resolve) {
      self.gitlab.projects.all(function (projects) {
        var results = [];

        results = projects
          .filter(function (project) {

            if (options.byOwner || options.byName || options.byDescription) {
              var ownerMatch = options.byOwner && project.namespace.name.match(pattern);
              var nameMatch = options.byName && project.name.match(pattern);
              var descriptionMatch = options.byDescription && project.description.match(pattern);
              return ownerMatch || nameMatch || descriptionMatch;
            }

            return project.name.match(pattern);
          })
          .map(function (project) {
            return {
              name: project.name,
              owner: project.namespace.name,
              description: project.description
            };
          });

        resolve(results);

      });
    })
      .then(function (results) {
        return Promise.all(results.map(function (result) {
          return self.validate(result.owner, result.name)
            .then(function (json) {
              json.owner = result.owner;
              json.name = result.name;
              return _.pick(json, ['owner', 'name', 'version', 'description']);
            })
            .catch(function () {
              return null;
            });
        }));
      })
      .then(function (results) {
        var filteredResults = _.reject(results, _.isNull);
        return _.sortBy(filteredResults, ['owner', 'name']);
      });
  }
};

module.exports = new GitlabRepo();<commit_msg>bug修复<commit_after>'use strict';

var path = require('path');
var http = require('http');
var https = require('https');
var url = require('url');

var _ = require('lodash');
var fs = require('graceful-fs');
var gitlab = require('gitlab');
var Promise = require('bluebird');
var semver = require('semver');
var ProgressBar = require('progress');

var fecom = require('../fecom');

var GitlabRepo = function () {
  this.gitlab = {};
  this.repo = null;
};

GitlabRepo.prototype = {
  constructor: GitlabRepo,
  initialize: function () {
    var self = this;
    self.gitlab.token = fecom.profile.token;
    self.gitlab = gitlab({
      token: fecom.profile.token,
      url: fecom.config.domain + '/api/v3'
    });
    self.repo = self.gitlab.projects.repository;
  },
  getArchive: function (namespace, projectName, tagName) {
    var archiveUrl = fecom.config.domain + '/' + namespace + '/' + projectName + '/repository/archive.zip?ref=' + tagName + '&private_token=' + fecom.profile.token;
    var prefix = namespace + '/' + projectName + '-' + tagName;
    var archivePath = path.join(fecom.tmpDir, prefix + '-archive.zip');
    archiveUrl = url.parse(archiveUrl);
    var client = ('http:' === archiveUrl.protocol ? http : https);
    var semantic = fecom.stringify({
      name: projectName,
      owner: namespace,
      version: tagName
    });

    return new Promise(function (resolve) {
      // fecom.logger.info('Begin to download ' + specified);
      // fecom.logger.info(archiveUrl.href);
      var req = client.request(archiveUrl);
      req.end();
      req.on('response', function (res) {
        var dirname = path.join(fecom.tmpDir, namespace);
        var contentLength = res.headers['content-length'] >> 0;
        var bar = new ProgressBar(fecom.i18n('DOWNLOADING_COMPONENT', semantic), {
          complete: '=',
          incomplete: ' ',
          width: 20,
          total: contentLength
        });

        if (!fs.existsSync(dirname)) {
          fs.mkdirSync(dirname);
        }

        var stream = fs.createWriteStream(archivePath);
        res.on('data', function (chunk) {
          bar.tick(chunk.length);
          stream.write(chunk);
        });
        res.on('end', function () {
          stream.on('finish', function () {
            // fecom.logger.info('Finish downloading ' + prefix);
            resolve(archivePath);
          });
          stream.end();
        });
      });
    });

  },
  validate: function (namespace, projectName, version) {
    var self = this;
    var latestTag = '';
    var promise = version ? fecom.async(version) : self.getLatestTag(namespace, projectName).then(function (tag) { return tag.name; });

    return promise
      .then(function (tagName) {
        latestTag = tagName;
        return self.getComponentJson(namespace, projectName, tagName);
      })
      .then(function (json) {

        if (json.version !== latestTag) {
          throw new Error(fecom.i18n('ERROR_INVALID_COMPONENT', projectName));
        }

        return json;
      });
  },
  getDependencies: function (namespace, projectName, version) {
    // Get dependencies
    var self = this;
    var node = {};

    return self.validate(namespace, projectName, version)
      .then(function (json) {
        json.dependencies = json.dependencies || [];
        version = version || json.version;
        node.name = fecom.stringify({
          name: projectName,
          owner: namespace,
          version: version
        });
        node.dependencies = json.dependencies.map(function (specified) {
          return fecom.stringify(fecom.parse(specified));
        });

        return node;
      });
  },
  getAllTags: function (namespace, projectName) {
    var self = this;
    var projectId = namespace + '/' + projectName;
    return new Promise(function (resolve, reject) {
      self.repo.listTags(projectId, function (tags) {
        if (!tags) {
          reject(new Error('Repository not found'));
          return false;
        }

        if (tags && !tags.length) {
          reject(new Error('Component tags not found in ' + projectId));
          return false;
        }

        resolve(tags);
      });
    });
  },
  getLatestTag: function (namespace, projectName) {
    var self = this;
    return self.getAllTags(namespace, projectName)
      .then(function (tags) {
        var latest = {};
        latest = _(tags).sortBy('commit.committed_date').last();
        return latest;
      });
  },
  getComponentJson: function (namespace, projectName, tagName) {
    var self = this;
    return new Promise(function (resolve, reject) {
      var projectId = namespace + '/' + projectName;
      self.repo.showFile({
        projectId: projectId,
        ref: 'tags/' + tagName,
        file_path: 'component.json'
      }, function (file) {
        var content, json;

        if (!file) {
          reject(new Error('"component.json" not found in ' + projectId));
          return false;
        }

        content = (new Buffer(file.content, 'base64')).toString();

        try {
          json = JSON.parse(content);
        } catch (err) {
          reject(err);
          return false;
        }

        resolve(json);
      });
    });
  },
  searchComponents: function (pattern, options) {
    var self = this;
    options = options || {};

    return new Promise(function (resolve) {
      self.gitlab.projects.all(function (projects) {
        var results = [];

        results = projects
          .filter(function (project) {

            if (options.byOwner || options.byName || options.byDescription) {
              var ownerMatch = options.byOwner && project.namespace.name.match(pattern);
              var nameMatch = options.byName && project.name.match(pattern);
              var descriptionMatch = options.byDescription && project.description.match(pattern);
              return ownerMatch || nameMatch || descriptionMatch;
            }

            return project.name.match(pattern);
          })
          .map(function (project) {
            return {
              name: project.name,
              owner: project.namespace.name,
              description: project.description
            };
          });

        resolve(results);

      });
    })
      .then(function (results) {
        return Promise.all(results.map(function (result) {
          return self.validate(result.owner, result.name)
            .then(function (json) {
              json.owner = result.owner;
              json.name = result.name;
              return _.pick(json, ['owner', 'name', 'version', 'description']);
            })
            .catch(function () {
              return null;
            });
        }));
      })
      .then(function (results) {
        var filteredResults = _.reject(results, _.isNull);
        return _.sortBy(filteredResults, ['owner', 'name']);
      });
  }
};

module.exports = new GitlabRepo();<|endoftext|>


<commit_before>'use strict'

const events = require('events')
const path = require('path')
const Module = require('module')

// We modified the original process.argv to let node.js load the
// atom-renderer.js, we need to restore it here.
process.argv.splice(1, 1)

// Clear search paths.
require('../common/reset-search-paths')

// Import common settings.
require('../common/init')

var globalPaths = Module.globalPaths

// Expose public APIs.
globalPaths.push(path.join(__dirname, 'api', 'exports'))

// The global variable will be used by ipc for event dispatching
var v8Util = process.atomBinding('v8_util')

v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter())

// Use electron module after everything is ready.
const electron = require('electron')

// Call webFrame method.
electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, method, args) => {
  electron.webFrame[method].apply(electron.webFrame, args)
})

electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (event, requestId, method, args) => {
  const responseCallback = function (result) {
    event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, result)
  }
  args.push(responseCallback)
  electron.webFrame[method].apply(electron.webFrame, args)
})

// Process command line arguments.
var nodeIntegration = 'false'
var preloadScript = null
for (let arg of process.argv) {
  if (arg.indexOf('--guest-instance-id=') === 0) {
    // This is a guest web view.
    process.guestInstanceId = parseInt(arg.substr(arg.indexOf('=') + 1))
  } else if (arg.indexOf('--opener-id=') === 0) {
    // This is a guest BrowserWindow.
    process.openerId = parseInt(arg.substr(arg.indexOf('=') + 1))
  } else if (arg.indexOf('--node-integration=') === 0) {
    nodeIntegration = arg.substr(arg.indexOf('=') + 1)
  } else if (arg.indexOf('--preload=') === 0) {
    preloadScript = arg.substr(arg.indexOf('=') + 1)
  }
}

if (window.location.protocol === 'chrome-devtools:') {
  // Override some inspector APIs.
  require('./inspector')
  nodeIntegration = 'true'
} else if (window.location.protocol === 'chrome-extension:') {
  // Add implementations of chrome API.
  require('./chrome-api')
  nodeIntegration = 'true'
} else {
  // Override default web functions.
  require('./override')

  // Load webview tag implementation.
  if (nodeIntegration === 'true' && process.guestInstanceId == null) {
    require('./web-view/web-view')
    require('./web-view/web-view-attributes')
  }
}

if (nodeIntegration === 'true') {
  // Export node bindings to global.
  global.require = require
  global.module = module

  // Set the __filename to the path of html file if it is file: protocol.
  if (window.location.protocol === 'file:') {
    var pathname = process.platform === 'win32' && window.location.pathname[0] === '/' ? window.location.pathname.substr(1) : window.location.pathname
    global.__filename = path.normalize(decodeURIComponent(pathname))
    global.__dirname = path.dirname(global.__filename)

    // Set module's filename so relative require can work as expected.
    module.filename = global.__filename

    // Also search for module under the html file.
    module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname))
  } else {
    global.__filename = __filename
    global.__dirname = __dirname
  }

  // Redirect window.onerror to uncaughtException.
  window.onerror = function (message, filename, lineno, colno, error) {
    if (global.process.listeners('uncaughtException').length > 0) {
      global.process.emit('uncaughtException', error)
      return true
    } else {
      return false
    }
  }
} else {
  // Delete Node's symbols after the Environment has been loaded.
  process.once('loaded', function () {
    delete global.process
    delete global.setImmediate
    delete global.clearImmediate
    return delete global.global
  })
}

// Load the script specfied by the "preload" attribute.
if (preloadScript) {
  try {
    require(preloadScript)
  } catch (error) {
    if (error.code === 'MODULE_NOT_FOUND' && error.message.indexOf(preloadScript) > -1) {
      console.error('Unable to load preload script ' + preloadScript)
    } else {
      console.error(error)
      console.error(error.stack)
    }
  }
}
<commit_msg>:art: Use the same stack trace for both cases<commit_after>'use strict'

const events = require('events')
const path = require('path')
const Module = require('module')

// We modified the original process.argv to let node.js load the
// atom-renderer.js, we need to restore it here.
process.argv.splice(1, 1)

// Clear search paths.
require('../common/reset-search-paths')

// Import common settings.
require('../common/init')

var globalPaths = Module.globalPaths

// Expose public APIs.
globalPaths.push(path.join(__dirname, 'api', 'exports'))

// The global variable will be used by ipc for event dispatching
var v8Util = process.atomBinding('v8_util')

v8Util.setHiddenValue(global, 'ipc', new events.EventEmitter())

// Use electron module after everything is ready.
const electron = require('electron')

// Call webFrame method.
electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', (event, method, args) => {
  electron.webFrame[method].apply(electron.webFrame, args)
})

electron.ipcRenderer.on('ELECTRON_INTERNAL_RENDERER_ASYNC_WEB_FRAME_METHOD', (event, requestId, method, args) => {
  const responseCallback = function (result) {
    event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, result)
  }
  args.push(responseCallback)
  electron.webFrame[method].apply(electron.webFrame, args)
})

// Process command line arguments.
var nodeIntegration = 'false'
var preloadScript = null
for (let arg of process.argv) {
  if (arg.indexOf('--guest-instance-id=') === 0) {
    // This is a guest web view.
    process.guestInstanceId = parseInt(arg.substr(arg.indexOf('=') + 1))
  } else if (arg.indexOf('--opener-id=') === 0) {
    // This is a guest BrowserWindow.
    process.openerId = parseInt(arg.substr(arg.indexOf('=') + 1))
  } else if (arg.indexOf('--node-integration=') === 0) {
    nodeIntegration = arg.substr(arg.indexOf('=') + 1)
  } else if (arg.indexOf('--preload=') === 0) {
    preloadScript = arg.substr(arg.indexOf('=') + 1)
  }
}

if (window.location.protocol === 'chrome-devtools:') {
  // Override some inspector APIs.
  require('./inspector')
  nodeIntegration = 'true'
} else if (window.location.protocol === 'chrome-extension:') {
  // Add implementations of chrome API.
  require('./chrome-api')
  nodeIntegration = 'true'
} else {
  // Override default web functions.
  require('./override')

  // Load webview tag implementation.
  if (nodeIntegration === 'true' && process.guestInstanceId == null) {
    require('./web-view/web-view')
    require('./web-view/web-view-attributes')
  }
}

if (nodeIntegration === 'true') {
  // Export node bindings to global.
  global.require = require
  global.module = module

  // Set the __filename to the path of html file if it is file: protocol.
  if (window.location.protocol === 'file:') {
    var pathname = process.platform === 'win32' && window.location.pathname[0] === '/' ? window.location.pathname.substr(1) : window.location.pathname
    global.__filename = path.normalize(decodeURIComponent(pathname))
    global.__dirname = path.dirname(global.__filename)

    // Set module's filename so relative require can work as expected.
    module.filename = global.__filename

    // Also search for module under the html file.
    module.paths = module.paths.concat(Module._nodeModulePaths(global.__dirname))
  } else {
    global.__filename = __filename
    global.__dirname = __dirname
  }

  // Redirect window.onerror to uncaughtException.
  window.onerror = function (message, filename, lineno, colno, error) {
    if (global.process.listeners('uncaughtException').length > 0) {
      global.process.emit('uncaughtException', error)
      return true
    } else {
      return false
    }
  }
} else {
  // Delete Node's symbols after the Environment has been loaded.
  process.once('loaded', function () {
    delete global.process
    delete global.setImmediate
    delete global.clearImmediate
    return delete global.global
  })
}

// Load the script specfied by the "preload" attribute.
if (preloadScript) {
  try {
    require(preloadScript)
  } catch (error) {
    console.error('Unable to load preload script: ' + preloadScript)
    console.error(error.stack || error.message)
  }
}
<|endoftext|>


<commit_before>var Handlebars = require('handlebars');
var haversine = require('haversine');
var log = require('./log')('request-views');
var select2 = require('select2');

var BikeView = require('./bike_views');
var geocoder = require('./geocoder');

var requestFormTemplate = Handlebars.compile(require('./request-form.html'));

var RequestFormView = module.exports = Backbone.View.extend({

  events: {
    'change .apiParam': 'changeForm',
    'click .search-button': 'changeForm',
    'change #mode': 'updateModeControls',
    'click .toggleSettings': 'toggleSettings'
  },

  initialize: function(options) {
    this.options = options || {};
    _.bindAll(this, 'changeForm');

    var view = this;

    this.updatingForm = false;

    this.geocodeItem = Handlebars.compile([
      '{{text}}',
      '{{#if city}}{{city}}, {{state}}{{/if}}'
    ].join('\n'));

    this.selectedGeocodeItem = Handlebars.compile([
      '{{text}}{{#if city}}, {{city}}, {{state}}{{/if}}'
    ].join('\n'));

    this.listenTo(this.model, 'change', function(data) {
      if (_.has(data.changed, 'fromPlace') && data.attributes.fromPlace && view.selectFrom) {
        view.updateReverseGeocoder('From', data.attributes.fromPlace, view.selectFrom);
      }

      if (_.has(data.changed, 'toPlace') && data.attributes.toPlace && view.selectTo) {
        view.updateReverseGeocoder('To', data.attributes.toPlace, view.selectTo);
      }

      view.$('#arriveBy').val(data.attributes.arriveBy);
      view.$('#mode').val(data.attributes.mode);
      view.$('#maxWalkDistance').val(data.attributes.maxWalkDistance);
      view.$('#optimize').val(data.attributes.optimize);

      var date = moment(data.attributes.date, 'MM-DD-YYYY').toDate();
      view.datepicker.setDate(date);

      var time = moment(data.attributes.time, 'hh:mm a').toDate();
      view.timepicker.setDate(time);

      view.updateModeControls();
    });

    this.listenTo(this.model, 'requesting', function() {
      view.deactivateSearchButton();
    });

    this.listenTo(this.model, 'success', function() {
      view.activateSearchButton();
    });

    this.listenTo(this.model, 'failure', function() {
      view.activateSearchButton();
    });
  },

  updateReverseGeocoder: function(field, latlon, select) {
    var view = this;

    if (window.OTP_config.reverseGeocode) {
      geocoder.reverse(latlon, function(err, results) {
        view.updatingForm = true;
        if (err) {
          select.select2('data', []);
        } else {
          select.select2('data', null);
          results.text = field + ' ' + results.address;
          select.select2('data', results);
        }
        view.updatingForm = false;
      });
    } else {

      // this isn't great but appears to be neccesary as selectize triggers a change event even when programatically updated
      view.updatingForm = true;

      var item = {
        text: field + ' marker location',
        id: latlon
      };

      select.select2('data', item);

      view.updatingForm = false;
    }
  },

  render: function() {

    var view = this;

    var html = requestFormTemplate({
      metric: this.options.metric || false
    });

    this.lastResults = [];

    this.$el.html(html);

    this.$('#showSettings').hide();

    view.updatingForm = true;

    this.$('#date').datetimepicker({
      pickTime: false
    });

    this.datepicker = this.$('#date').data('DateTimePicker');
    this.datepicker.setDate(new Date());
    this.$('#date').on('dp.change', function() {
      view.changeForm();
    });

    this.$('#time').datetimepicker({
      pick12HourFormat: true,
      pickSeconds: false,
      pickDate: false
    });

    this.timepicker = this.$('#time').data('DateTimePicker');
    this.timepicker.setDate(new Date());
    this.$('#time').on('dp.change', function() {
      view.changeForm();
    });

    this.bikeTriangle = new BikeView({
      model: this.model,
      el: this.$('#bikeTriangle')
    });

    this.selectFrom = this.initializeSelectFor('from');
    this.selectTo = this.initializeSelectFor('to');

    view.updatingForm = false;

    this.changeForm();
    this.updateModeControls();
  },

  changeForm: function(evt) {

    // skip duplicate change events caused by selectize form inputs
    if (this.updatingForm)
      return;

    var maxDistance = $('#mode').val().indexOf('WALK') != -1 ?
      $('#maxWalkDistance').val() : $('#maxBikeDistance').val();

    var data = {
      fromPlace: this.$('#fromPlace').select2('val'),
      toPlace: this.$('#toPlace').select2('val'),
      date: this.$('#date input').val(),
      time: this.$('#time input').val(),
      arriveBy: this.$('#arriveBy').val(),
      maxWalkDistance: maxDistance,
      optimize: this.$('#optimize').val(),
      mode: this.$('#mode').val()
    };

    // skip if either to/from fields are unset
    if (this.$('#fromPlace').select2('val') == 'not found')
      data.fromPlace = false;
    if (this.$('#toPlace').select2('val') == 'not found')
      data.toPlace = false;

    var mode = $('#mode').val();
    if (mode.indexOf('BICYCLE') != -1) {
      data.triangleSafetyFactor = $('#').val();
      data.triangleSlopeFactor = $('#').val();
      data.triangleTimeFactor = $('#').val();
    } else {
      this.model.unset('triangleSafetyFactor', {
        silent: true
      });
      this.model.unset('triangleSlopeFactor', {
        silent: true
      });
      this.model.unset('triangleTimeFactor', {
        silent: true
      });
    }

    this.model.set(data);
  },

  deactivateSearchButton: function() {
    log('deactivating search button');

    this.$('.search-button').addClass('hidden');
    this.$('.searching-button').removeClass('hidden');
  },

  activateSearchButton: function() {
    log('activating search button');

    this.$('.search-button').removeClass('hidden');
    this.$('.searching-button').addClass('hidden');
  },

  updateModeControls: function() {
    var mode = $('#mode').val();

    //if (mode.indexOf('WALK') != -1 && mode.indexOf('TRANSIT') != -1) this.$el
    //  .find('.maxWalkControl').show();
    //else this.$el.find('.maxWalkControl').hide();

    // disabling maxWalkControl as we switch to soft walk dist limiting
    this.$el.find('.maxWalkControl').hide();

    if (mode.indexOf('BICYCLE') != -1 && mode.indexOf('TRANSIT') != -1) this
      .$el.find('.maxBikeControl').show();
    else this.$el.find('.maxBikeControl').hide();

    if (mode.indexOf('TRANSIT') != -1 && mode.indexOf('BICYCLE') == -1)
      this.$el.find('.optimizeControl').show();
    else
      this.$el.find('.optimizeControl').hide();

    if (mode.indexOf('BICYCLE') != -1) this.$el.find('.bikeTriangleControl')
      .show();
    else this.$el.find('.bikeTriangleControl').hide();
  },

  toggleSettings: function() {

    if ($('#hidableSettings').is(':visible')) {
      $('#hidableSettings').slideUp('fast', function() {
        $('#itineraries').height($(window).height() - ($('#request').height() +
          $('#messageWell').height() + 80));
      });
      $('#showSettings').show();
      $('#hideSettings').hide();
    } else {
      $('#hidableSettings').slideDown('fast', function() {
        $('#itineraries').height($(window).height() - ($('#request').height() +
          $('#messageWell').height() + 80));
      });
      $('#showSettings').hide();
      $('#hideSettings').show();
    }

  },

  reverse: function(place, error, success) {

    // esri takes lon/lat
    var parts = place.split(',');
    var lonlat = parts[1] + ',' + parts[0];

    $.ajax({
      url: window.OTP_config.esriApi + 'reverseGeocode?location=' +
        encodeURIComponent(lonlat) + '&f=pjson',
      type: 'GET',
      dataType: 'jsonp',
      error: function() {
        error();
      },
      success: function(res) {

        var data = {
          address: res.address.Address,
          city: res.address.City,
          state: res.address.Region,
          latlon: place
        };

        success(data);
      }
    });
  },

  initializeSelectFor: function(term) {
    var $id = '#' + term + 'Place';
    var placeholder = term === 'from' ? 'Start' : 'End';
    var url = window.OTP_config.esriApi + 'findAddressCandidates';
    var view = this;

    return this.$($id).select2({
      placeholder: placeholder + ' Address',
      minimumInputLength: 5,
      allowClear: true,
      selectOnBlur: true,
      createSearchChoice: function(term) {
        if (view.lastResults.length === 0) {
          var text = term + ' (not found)';
          return {
            id: 'not found',
            text: text
          };
        }
      },
      formatResult: function(object, container) {
        return view.geocodeItem(object);
      },
      formatSelection: function(object, container) {
        return view.selectedGeocodeItem(Object.assign({}, { text: term + ' ' + object.text }, object));
      },
      ajax: {
        url: url,
        dataType: 'json',
        quietMillis: 20,
        data: function(term, page) {
          view.lastResults = [];

          var query = {
            countryCode: 'USA',
            f: 'json',
            distance: 5000, // 5km
            singleLine: term,
            outFields: 'City,Region',
            location: '-73.7562271,42.6525795', // TODO: Should be from config
            maxLocations: 5
          };

          if (view.options.map) {
            var latlng = view.options.map.getCenter();
            query.location = latlng.lng + ',' + latlng.lat;

            var bounds = view.options.map.getBounds();
            var nw = bounds.getNorthWest();
            var se = bounds.getSouthEast();
            query.distance = haversine(nw.lat, nw.lng, se.lat, se.lng) * 1000;
          }

          return query;
        },
        results: function(res, page) {
          var results = [];
          if (res.candidates) {
            for (var i in res.candidates) {
              var candidate = res.candidates[i];
              var location = candidate.location;

              results.push({
                text: candidate.address.split(',')[0],
                city: candidate.attributes.City,
                state: candidate.attributes.Region,
                id: location.y + ',' + location.x
              });
            }
          }

          view.skipReverseGeocode = true;
          view.lastResults = results;

          if (results.length > 0) {
            view.$($id).select2('data', Object.assign({}, results[0], {
              text: term + ' ' + results[0].text
            }));
            view.changeForm();
          }

          return {
            results: results
          };
        }
      }
    });
  }
});
<commit_msg>Always show state<commit_after>var Handlebars = require('handlebars');
var haversine = require('haversine');
var log = require('./log')('request-views');
var select2 = require('select2');

var BikeView = require('./bike_views');
var geocoder = require('./geocoder');

var requestFormTemplate = Handlebars.compile(require('./request-form.html'));

var RequestFormView = module.exports = Backbone.View.extend({

  events: {
    'change .apiParam': 'changeForm',
    'click .search-button': 'changeForm',
    'change #mode': 'updateModeControls',
    'click .toggleSettings': 'toggleSettings'
  },

  initialize: function(options) {
    this.options = options || {};
    _.bindAll(this, 'changeForm');

    var view = this;

    this.updatingForm = false;

    this.geocodeItem = Handlebars.compile([
      '{{text}}',
      '{{#if city}}{{city}}, {{/if}}{{state}}'
    ].join('\n'));

    this.selectedGeocodeItem = Handlebars.compile([
      '{{text}}{{#if city}}, {{city}}{{/if}}, {{state}}'
    ].join('\n'));

    this.listenTo(this.model, 'change', function(data) {
      if (_.has(data.changed, 'fromPlace') && data.attributes.fromPlace && view.selectFrom) {
        view.updateReverseGeocoder('From', data.attributes.fromPlace, view.selectFrom);
      }

      if (_.has(data.changed, 'toPlace') && data.attributes.toPlace && view.selectTo) {
        view.updateReverseGeocoder('To', data.attributes.toPlace, view.selectTo);
      }

      view.$('#arriveBy').val(data.attributes.arriveBy);
      view.$('#mode').val(data.attributes.mode);
      view.$('#maxWalkDistance').val(data.attributes.maxWalkDistance);
      view.$('#optimize').val(data.attributes.optimize);

      var date = moment(data.attributes.date, 'MM-DD-YYYY').toDate();
      view.datepicker.setDate(date);

      var time = moment(data.attributes.time, 'hh:mm a').toDate();
      view.timepicker.setDate(time);

      view.updateModeControls();
    });

    this.listenTo(this.model, 'requesting', function() {
      view.deactivateSearchButton();
    });

    this.listenTo(this.model, 'success', function() {
      view.activateSearchButton();
    });

    this.listenTo(this.model, 'failure', function() {
      view.activateSearchButton();
    });
  },

  updateReverseGeocoder: function(field, latlon, select) {
    var view = this;

    if (window.OTP_config.reverseGeocode) {
      geocoder.reverse(latlon, function(err, results) {
        view.updatingForm = true;
        if (err) {
          select.select2('data', []);
        } else {
          select.select2('data', null);
          results.text = field + ' ' + results.address;
          select.select2('data', results);
        }
        view.updatingForm = false;
      });
    } else {

      // this isn't great but appears to be neccesary as selectize triggers a change event even when programatically updated
      view.updatingForm = true;

      var item = {
        text: field + ' marker location',
        id: latlon
      };

      select.select2('data', item);

      view.updatingForm = false;
    }
  },

  render: function() {

    var view = this;

    var html = requestFormTemplate({
      metric: this.options.metric || false
    });

    this.lastResults = [];

    this.$el.html(html);

    this.$('#showSettings').hide();

    view.updatingForm = true;

    this.$('#date').datetimepicker({
      pickTime: false
    });

    this.datepicker = this.$('#date').data('DateTimePicker');
    this.datepicker.setDate(new Date());
    this.$('#date').on('dp.change', function() {
      view.changeForm();
    });

    this.$('#time').datetimepicker({
      pick12HourFormat: true,
      pickSeconds: false,
      pickDate: false
    });

    this.timepicker = this.$('#time').data('DateTimePicker');
    this.timepicker.setDate(new Date());
    this.$('#time').on('dp.change', function() {
      view.changeForm();
    });

    this.bikeTriangle = new BikeView({
      model: this.model,
      el: this.$('#bikeTriangle')
    });

    this.selectFrom = this.initializeSelectFor('from');
    this.selectTo = this.initializeSelectFor('to');

    view.updatingForm = false;

    this.changeForm();
    this.updateModeControls();
  },

  changeForm: function(evt) {

    // skip duplicate change events caused by selectize form inputs
    if (this.updatingForm)
      return;

    var maxDistance = $('#mode').val().indexOf('WALK') != -1 ?
      $('#maxWalkDistance').val() : $('#maxBikeDistance').val();

    var data = {
      fromPlace: this.$('#fromPlace').select2('val'),
      toPlace: this.$('#toPlace').select2('val'),
      date: this.$('#date input').val(),
      time: this.$('#time input').val(),
      arriveBy: this.$('#arriveBy').val(),
      maxWalkDistance: maxDistance,
      optimize: this.$('#optimize').val(),
      mode: this.$('#mode').val()
    };

    // skip if either to/from fields are unset
    if (this.$('#fromPlace').select2('val') == 'not found')
      data.fromPlace = false;
    if (this.$('#toPlace').select2('val') == 'not found')
      data.toPlace = false;

    var mode = $('#mode').val();
    if (mode.indexOf('BICYCLE') != -1) {
      data.triangleSafetyFactor = $('#').val();
      data.triangleSlopeFactor = $('#').val();
      data.triangleTimeFactor = $('#').val();
    } else {
      this.model.unset('triangleSafetyFactor', {
        silent: true
      });
      this.model.unset('triangleSlopeFactor', {
        silent: true
      });
      this.model.unset('triangleTimeFactor', {
        silent: true
      });
    }

    this.model.set(data);
  },

  deactivateSearchButton: function() {
    log('deactivating search button');

    this.$('.search-button').addClass('hidden');
    this.$('.searching-button').removeClass('hidden');
  },

  activateSearchButton: function() {
    log('activating search button');

    this.$('.search-button').removeClass('hidden');
    this.$('.searching-button').addClass('hidden');
  },

  updateModeControls: function() {
    var mode = $('#mode').val();

    //if (mode.indexOf('WALK') != -1 && mode.indexOf('TRANSIT') != -1) this.$el
    //  .find('.maxWalkControl').show();
    //else this.$el.find('.maxWalkControl').hide();

    // disabling maxWalkControl as we switch to soft walk dist limiting
    this.$el.find('.maxWalkControl').hide();

    if (mode.indexOf('BICYCLE') != -1 && mode.indexOf('TRANSIT') != -1) this
      .$el.find('.maxBikeControl').show();
    else this.$el.find('.maxBikeControl').hide();

    if (mode.indexOf('TRANSIT') != -1 && mode.indexOf('BICYCLE') == -1)
      this.$el.find('.optimizeControl').show();
    else
      this.$el.find('.optimizeControl').hide();

    if (mode.indexOf('BICYCLE') != -1) this.$el.find('.bikeTriangleControl')
      .show();
    else this.$el.find('.bikeTriangleControl').hide();
  },

  toggleSettings: function() {

    if ($('#hidableSettings').is(':visible')) {
      $('#hidableSettings').slideUp('fast', function() {
        $('#itineraries').height($(window).height() - ($('#request').height() +
          $('#messageWell').height() + 80));
      });
      $('#showSettings').show();
      $('#hideSettings').hide();
    } else {
      $('#hidableSettings').slideDown('fast', function() {
        $('#itineraries').height($(window).height() - ($('#request').height() +
          $('#messageWell').height() + 80));
      });
      $('#showSettings').hide();
      $('#hideSettings').show();
    }

  },

  reverse: function(place, error, success) {

    // esri takes lon/lat
    var parts = place.split(',');
    var lonlat = parts[1] + ',' + parts[0];

    $.ajax({
      url: window.OTP_config.esriApi + 'reverseGeocode?location=' +
        encodeURIComponent(lonlat) + '&f=pjson',
      type: 'GET',
      dataType: 'jsonp',
      error: function() {
        error();
      },
      success: function(res) {

        var data = {
          address: res.address.Address,
          city: res.address.City,
          state: res.address.Region,
          latlon: place
        };

        success(data);
      }
    });
  },

  initializeSelectFor: function(term) {
    var $id = '#' + term + 'Place';
    var placeholder = term === 'from' ? 'Start' : 'End';
    var url = window.OTP_config.esriApi + 'findAddressCandidates';
    var view = this;

    return this.$($id).select2({
      placeholder: placeholder + ' Address',
      minimumInputLength: 5,
      allowClear: true,
      selectOnBlur: true,
      createSearchChoice: function(term) {
        if (view.lastResults.length === 0) {
          var text = term + ' (not found)';
          return {
            id: 'not found',
            text: text
          };
        }
      },
      formatResult: function(object, container) {
        return view.geocodeItem(object);
      },
      formatSelection: function(object, container) {
        return view.selectedGeocodeItem(Object.assign({}, { text: term + ' ' + object.text }, object));
      },
      ajax: {
        url: url,
        dataType: 'json',
        quietMillis: 20,
        data: function(term, page) {
          view.lastResults = [];

          var query = {
            countryCode: 'USA',
            f: 'json',
            distance: 5000, // 5km
            singleLine: term,
            outFields: 'City,Region',
            location: '-73.7562271,42.6525795', // TODO: Should be from config
            maxLocations: 5
          };

          if (view.options.map) {
            var latlng = view.options.map.getCenter();
            query.location = latlng.lng + ',' + latlng.lat;

            var bounds = view.options.map.getBounds();
            var nw = bounds.getNorthWest();
            var se = bounds.getSouthEast();
            query.distance = haversine(nw.lat, nw.lng, se.lat, se.lng) * 1000;
          }

          return query;
        },
        results: function(res, page) {
          var results = [];
          if (res.candidates) {
            console.log('results', res.candidates);
            for (var i in res.candidates) {
              var candidate = res.candidates[i];
              var location = candidate.location;

              results.push({
                text: candidate.address.split(',')[0],
                city: candidate.attributes.City,
                state: candidate.attributes.Region,
                id: location.y + ',' + location.x
              });
            }
          }

          view.skipReverseGeocode = true;
          view.lastResults = results;

          if (results.length > 0) {
            view.$($id).select2('data', Object.assign({}, results[0], {
              text: term + ' ' + results[0].text
            }));
            view.changeForm();
          }

          return {
            results: results
          };
        }
      }
    });
  }
});
<|endoftext|>


<commit_before>'use strict';

var debug = require('debug')('saga:revisionGuard'),
  _ = require('lodash'),
  Queue = require('./orderQueue'),
  ConcurrencyError = require('./errors/concurrencyError'),
  AlreadyHandledError = require('./errors/alreadyHandledError'),
  AlreadyHandlingError = require('./errors/alreadyHandlingError'),
  dotty = require('dotty');

/**
 * RevisionGuard constructor
 * @param {Object} store   The store object.
 * @param {Object} options The options object.
 * @constructor
 */
function RevisionGuard (store, options) {
  options = options || {};

  var defaults = {
    queueTimeout: 1000,
    queueTimeoutMaxLoops: 3//,
    // startRevisionNumber: 1
  };

  _.defaults(options, defaults);

  this.options = options;

  if (!store || !_.isObject(store)) {
    var err = new Error('store not injected!');
    debug(err);
    throw err;
  }

  this.store = store;

  this.definition = {
    correlationId: 'correlationId', // optional
    id: 'id',                       // optional
    name: 'name',                   // optional
//    aggregateId: 'aggregate.id',    // optional
//    context: 'context.name',        // optional
//    aggregate: 'aggregate.name',    // optional
    payload: 'payload'              // optional
//    revision: 'revision'            // optional
//    version: 'version',             // optional
//    meta: 'meta'                    // optional, if defined theses values will be copied to the notification (can be used to transport information like userId, etc..)
  };

  this.queue = new Queue({ queueTimeout: this.options.queueTimeout });

  this.currentHandlingRevisions = {};

  this.onEventMissing(function (info, evt) {
    debug('missing events: ', info, evt);
  });
}

/**
 * Returns a random number between passed values of min and max.
 * @param {Number} min The minimum value of the resulting random number.
 * @param {Number} max The maximum value of the resulting random number.
 * @returns {Number}
 */
function randomBetween(min, max) {
  return Math.round(min + Math.random() * (max - min));
}

RevisionGuard.prototype = {

  /**
   * Inject definition for event structure.
   * @param   {Object} definition the definition to be injected
   */
  defineEvent: function (definition) {
    if (!_.isObject(definition)) {
      throw new Error('Please pass in an object');
    }
    this.definition = _.defaults(definition, this.definition);
    return this;
  },

  /**
   * Inject function for event missing handle.
   * @param   {Function} fn       the function to be injected
   * @returns {RevisionGuard} to be able to chain...
   */
  onEventMissing: function (fn) {
    if (!fn || !_.isFunction(fn)) {
      var err = new Error('Please pass a valid function!');
      debug(err);
      throw err;
    }

    if (fn.length === 1) {
      fn = _.wrap(fn, function(func, info, evt, callback) {
        func(info, evt);
        callback(null);
      });
    }

    this.onEventMissingHandle = fn;

    return this;
  },

  /**
   * Returns the concatenated id (more unique)
   * @param {Object}   evt The passed eventt.
   * @returns {string}
   */
  getConcatenatedId: function (evt) {
    var aggregateId = '';
    if (dotty.exists(evt, this.definition.aggregateId)) {
      aggregateId = dotty.get(evt, this.definition.aggregateId);
    }

    var aggregate = '';
    if (dotty.exists(evt, this.definition.aggregate)) {
      aggregate = dotty.get(evt, this.definition.aggregate);
    }

    var context = '';
    if (dotty.exists(evt, this.definition.context)) {
      context = dotty.get(evt, this.definition.context);
    }

    return context + aggregate + aggregateId;
  },

  /**
   * Queues an event with its callback by aggregateId
   * @param {Object}   evt      The event object.
   * @param {Function} callback The event callback.
   */
  queueEvent: function (evt, callback) {
    var self = this;
    var evtId = dotty.get(evt, this.definition.id);
    var revInEvt = dotty.get(evt, this.definition.revision);
    var aggId = dotty.get(evt, this.definition.aggregateId);

    var concatenatedId = this.getConcatenatedId(evt);

    this.queue.push(concatenatedId, evtId, evt, callback, function (loopCount, waitAgain) {
      self.store.get(concatenatedId, function (err, revInStore) {
        if (err) {
          debug(err);
          self.store.remove(concatenatedId, evtId);
          return callback(err);
        }

        if (revInEvt === revInStore) {
          debug('revision match [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
          return;
        }

        if (loopCount < self.options.queueTimeoutMaxLoops) {
          debug('revision mismatch => try/wait again... [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
          return waitAgain();
        }

        debug('event timeouted [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
        // try to replay depending from id and evt...
        var info = {
          aggregateId: aggId,
          aggregateRevision: !!self.definition.revision ? dotty.get(evt, self.definition.revision) : undefined,
          aggregate: !!self.definition.aggregate ? dotty.get(evt, self.definition.aggregate) : undefined,
          context: !!self.definition.context ? dotty.get(evt, self.definition.context) : undefined,
          guardRevision: revInStore || self.options.startRevisionNumber
        };
        self.onEventMissingHandle(info, evt);
      });
    });
  },

  /**
   * Finishes the guard stuff and save the new revision to store.
   * @param {Object}   evt        The event object.
   * @param {Number}   revInStore The actual revision number in store.
   * @param {Function} callback   The function, that will be called when this action is completed.
   *                              `function(err){}`
   */
  finishGuard: function (evt, revInStore, callback) {
    var evtId = dotty.get(evt, this.definition.id);
    var revInEvt = dotty.get(evt, this.definition.revision);

    var self = this;

    var concatenatedId = this.getConcatenatedId(evt);

    this.store.set(concatenatedId, revInEvt + 1, revInStore, function (err) {
      if (err) {
        debug(err);
        if (err instanceof ConcurrencyError) {
          var retryIn = randomBetween(0, self.options.retryOnConcurrencyTimeout || 800);
          debug('retry in ' + retryIn + 'ms for [concatenatedId]=' + concatenatedId + ', [revInStore]=' + (revInStore || 'null') + ', [revInEvt]=' + revInEvt);
          setTimeout(function() {
            self.guard(evt, callback);
          }, retryIn);
          return;
        }

        return callback(err);
      }

      self.store.saveLastEvent(evt, function (err) {
        if (err) {
          debug('error while saving last event');
          debug(err);
        }
      });

      self.queue.remove(concatenatedId, evtId);
      callback(null);

      var pendingEvents = self.queue.get(concatenatedId);
      if (!pendingEvents || pendingEvents.length === 0) return debug('no other pending event found [concatenatedId]=' + concatenatedId + ', [revInStore]=' + (revInStore || 'null') + ', [revInEvt]=' + revInEvt);

      var nextEvent = _.find(pendingEvents, function (e) {
        var revInNextEvt = dotty.get(e.payload, self.definition.revision);
        return revInNextEvt === revInEvt + 1;
      });

      if (!nextEvent) return debug('no next pending event found [concatenatedId]=' + concatenatedId + ', [revInStore]=' + (revInStore || 'null') + ', [revInEvt]=' + revInEvt);

      debug('found next pending event => guard [concatenatedId]=' + concatenatedId + ', [revInStore]=' + (revInStore || 'null') + ', [revInEvt]=' + revInEvt);
      self.guard(nextEvent.payload, nextEvent.callback);
    });
  },

  /**
   * Guard this event. Check for order and check if missing events...
   * @param {Object} evt The event object.
   * @param {Function} callback The event callback.
   */
  guard: function (evt, callback) {
    if (!this.definition.aggregateId || !dotty.exists(evt, this.definition.aggregateId) ||
        !this.definition.revision || !dotty.exists(evt, this.definition.revision)) {
      var err = new Error('Please define an aggregateId!');
      debug(err);
      return callback(err);
    }

    var self = this;

    var revInEvt = dotty.get(evt, this.definition.revision);

    var concatenatedId = this.getConcatenatedId(evt);

    function proceed (revInStore) {
      if (!revInStore && !self.currentHandlingRevisions[concatenatedId] && (self.options.startRevisionNumber === undefined || self.options.startRevisionNumber === null)) {
        self.currentHandlingRevisions[concatenatedId] = revInEvt;
        debug('first revision to store [concatenatedId]=' + concatenatedId + ', [revInStore]=' + (revInStore || 'null') + ', [revInEvt]=' + revInEvt);
        callback(null, function (clb) {
          self.finishGuard(evt, revInStore, clb);
        });
        return;
      }

      if (revInStore && revInEvt < revInStore) {
        debug('event already handled [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
        callback(new AlreadyHandledError('Event: [id]=' + dotty.get(evt, self.definition.id) + ', [revision]=' + revInEvt + ', [concatenatedId]=' + concatenatedId + ' already handled!'), function (clb) {
          clb(null);
        });
        return;
      }

      if (revInStore && revInEvt > revInStore) {
        debug('queue event [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
        self.queueEvent(evt, callback);
        return;
      }

      if (!revInStore && self.options.startRevisionNumber >= 0 && revInEvt > self.options.startRevisionNumber) {
        debug('queue event (startRevisionNumber is set) [concatenatedId]=' + concatenatedId + ', [startRevisionNumber]=' + self.options.startRevisionNumber + ', [revInEvt]=' + revInEvt);
        self.queueEvent(evt, callback);
        return;
      }

      if (!revInStore && revInEvt > self.currentHandlingRevisions[concatenatedId]) {
        debug('queue event [concatenatedId]=' + concatenatedId + ', [currentlyHandling]=' + self.currentHandlingRevisions[concatenatedId] + ', [revInEvt]=' + revInEvt);
        self.queueEvent(evt, callback);
        return;
      }

      if (self.currentHandlingRevisions[concatenatedId] >= revInEvt) {
        debug('event already handling [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
        callback(new AlreadyHandlingError('Event: [id]=' + dotty.get(evt, self.definition.id) + ', [revision]=' + revInEvt + ', [concatenatedId]=' + concatenatedId + ' already handling!'), function (clb) {
          clb(null);
        });
        return;
      }

      self.currentHandlingRevisions[concatenatedId] = revInEvt;

      debug('event is in correct order => go for it! [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
      callback(null, function (clb) {
        self.finishGuard(evt, revInStore, clb);
      });
    }

    function retry (max, loop) {
      setTimeout(function () {
        self.store.get(concatenatedId, function(err, revInStore) {
          if (err) {
            debug(err);
            return callback(err);
          }

          if (loop <= 0) {
            debug('finished loops for retry => proceed [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
            return proceed(revInStore);
          }

          if (!revInStore && revInEvt !== 1) {
            debug('no revision in store => retry [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
            retry(max, --loop);
            return;
          }

          debug('revision in store existing => proceed [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
          proceed(revInStore);
        });
      }, randomBetween(max / 5, max));
    }

    process.nextTick(function () {
      self.store.get(concatenatedId, function (err, revInStore) {
        if (err) {
          debug(err);
          return callback(err);
        }

        if (!revInStore && revInEvt !== 1 && !self.currentHandlingRevisions[concatenatedId]) {
          var max = (self.options.queueTimeout * self.options.queueTimeoutMaxLoops) / 3;
          max = max < 10 ? 10 : max;
          retry(max, self.options.queueTimeoutMaxLoops);
          return;
        }

        proceed(revInStore);
      });
    });
  }

};

module.exports = RevisionGuard;
<commit_msg>(self.options.startRevisionNumber || 1)<commit_after>'use strict';

var debug = require('debug')('saga:revisionGuard'),
  _ = require('lodash'),
  Queue = require('./orderQueue'),
  ConcurrencyError = require('./errors/concurrencyError'),
  AlreadyHandledError = require('./errors/alreadyHandledError'),
  AlreadyHandlingError = require('./errors/alreadyHandlingError'),
  dotty = require('dotty');

/**
 * RevisionGuard constructor
 * @param {Object} store   The store object.
 * @param {Object} options The options object.
 * @constructor
 */
function RevisionGuard (store, options) {
  options = options || {};

  var defaults = {
    queueTimeout: 1000,
    queueTimeoutMaxLoops: 3//,
    // startRevisionNumber: 1
  };

  _.defaults(options, defaults);

  this.options = options;

  if (!store || !_.isObject(store)) {
    var err = new Error('store not injected!');
    debug(err);
    throw err;
  }

  this.store = store;

  this.definition = {
    correlationId: 'correlationId', // optional
    id: 'id',                       // optional
    name: 'name',                   // optional
//    aggregateId: 'aggregate.id',    // optional
//    context: 'context.name',        // optional
//    aggregate: 'aggregate.name',    // optional
    payload: 'payload'              // optional
//    revision: 'revision'            // optional
//    version: 'version',             // optional
//    meta: 'meta'                    // optional, if defined theses values will be copied to the notification (can be used to transport information like userId, etc..)
  };

  this.queue = new Queue({ queueTimeout: this.options.queueTimeout });

  this.currentHandlingRevisions = {};

  this.onEventMissing(function (info, evt) {
    debug('missing events: ', info, evt);
  });
}

/**
 * Returns a random number between passed values of min and max.
 * @param {Number} min The minimum value of the resulting random number.
 * @param {Number} max The maximum value of the resulting random number.
 * @returns {Number}
 */
function randomBetween(min, max) {
  return Math.round(min + Math.random() * (max - min));
}

RevisionGuard.prototype = {

  /**
   * Inject definition for event structure.
   * @param   {Object} definition the definition to be injected
   */
  defineEvent: function (definition) {
    if (!_.isObject(definition)) {
      throw new Error('Please pass in an object');
    }
    this.definition = _.defaults(definition, this.definition);
    return this;
  },

  /**
   * Inject function for event missing handle.
   * @param   {Function} fn       the function to be injected
   * @returns {RevisionGuard} to be able to chain...
   */
  onEventMissing: function (fn) {
    if (!fn || !_.isFunction(fn)) {
      var err = new Error('Please pass a valid function!');
      debug(err);
      throw err;
    }

    if (fn.length === 1) {
      fn = _.wrap(fn, function(func, info, evt, callback) {
        func(info, evt);
        callback(null);
      });
    }

    this.onEventMissingHandle = fn;

    return this;
  },

  /**
   * Returns the concatenated id (more unique)
   * @param {Object}   evt The passed eventt.
   * @returns {string}
   */
  getConcatenatedId: function (evt) {
    var aggregateId = '';
    if (dotty.exists(evt, this.definition.aggregateId)) {
      aggregateId = dotty.get(evt, this.definition.aggregateId);
    }

    var aggregate = '';
    if (dotty.exists(evt, this.definition.aggregate)) {
      aggregate = dotty.get(evt, this.definition.aggregate);
    }

    var context = '';
    if (dotty.exists(evt, this.definition.context)) {
      context = dotty.get(evt, this.definition.context);
    }

    return context + aggregate + aggregateId;
  },

  /**
   * Queues an event with its callback by aggregateId
   * @param {Object}   evt      The event object.
   * @param {Function} callback The event callback.
   */
  queueEvent: function (evt, callback) {
    var self = this;
    var evtId = dotty.get(evt, this.definition.id);
    var revInEvt = dotty.get(evt, this.definition.revision);
    var aggId = dotty.get(evt, this.definition.aggregateId);

    var concatenatedId = this.getConcatenatedId(evt);

    this.queue.push(concatenatedId, evtId, evt, callback, function (loopCount, waitAgain) {
      self.store.get(concatenatedId, function (err, revInStore) {
        if (err) {
          debug(err);
          self.store.remove(concatenatedId, evtId);
          return callback(err);
        }

        if (revInEvt === revInStore) {
          debug('revision match [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
          return;
        }

        if (loopCount < self.options.queueTimeoutMaxLoops) {
          debug('revision mismatch => try/wait again... [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
          return waitAgain();
        }

        debug('event timeouted [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
        // try to replay depending from id and evt...
        var info = {
          aggregateId: aggId,
          aggregateRevision: !!self.definition.revision ? dotty.get(evt, self.definition.revision) : undefined,
          aggregate: !!self.definition.aggregate ? dotty.get(evt, self.definition.aggregate) : undefined,
          context: !!self.definition.context ? dotty.get(evt, self.definition.context) : undefined,
          guardRevision: revInStore || self.options.startRevisionNumber
        };
        self.onEventMissingHandle(info, evt);
      });
    });
  },

  /**
   * Finishes the guard stuff and save the new revision to store.
   * @param {Object}   evt        The event object.
   * @param {Number}   revInStore The actual revision number in store.
   * @param {Function} callback   The function, that will be called when this action is completed.
   *                              `function(err){}`
   */
  finishGuard: function (evt, revInStore, callback) {
    var evtId = dotty.get(evt, this.definition.id);
    var revInEvt = dotty.get(evt, this.definition.revision);

    var self = this;

    var concatenatedId = this.getConcatenatedId(evt);

    this.store.set(concatenatedId, revInEvt + 1, revInStore, function (err) {
      if (err) {
        debug(err);
        if (err instanceof ConcurrencyError) {
          var retryIn = randomBetween(0, self.options.retryOnConcurrencyTimeout || 800);
          debug('retry in ' + retryIn + 'ms for [concatenatedId]=' + concatenatedId + ', [revInStore]=' + (revInStore || 'null') + ', [revInEvt]=' + revInEvt);
          setTimeout(function() {
            self.guard(evt, callback);
          }, retryIn);
          return;
        }

        return callback(err);
      }

      self.store.saveLastEvent(evt, function (err) {
        if (err) {
          debug('error while saving last event');
          debug(err);
        }
      });

      self.queue.remove(concatenatedId, evtId);
      callback(null);

      var pendingEvents = self.queue.get(concatenatedId);
      if (!pendingEvents || pendingEvents.length === 0) return debug('no other pending event found [concatenatedId]=' + concatenatedId + ', [revInStore]=' + (revInStore || 'null') + ', [revInEvt]=' + revInEvt);

      var nextEvent = _.find(pendingEvents, function (e) {
        var revInNextEvt = dotty.get(e.payload, self.definition.revision);
        return revInNextEvt === revInEvt + 1;
      });

      if (!nextEvent) return debug('no next pending event found [concatenatedId]=' + concatenatedId + ', [revInStore]=' + (revInStore || 'null') + ', [revInEvt]=' + revInEvt);

      debug('found next pending event => guard [concatenatedId]=' + concatenatedId + ', [revInStore]=' + (revInStore || 'null') + ', [revInEvt]=' + revInEvt);
      self.guard(nextEvent.payload, nextEvent.callback);
    });
  },

  /**
   * Guard this event. Check for order and check if missing events...
   * @param {Object} evt The event object.
   * @param {Function} callback The event callback.
   */
  guard: function (evt, callback) {
    if (!this.definition.aggregateId || !dotty.exists(evt, this.definition.aggregateId) ||
        !this.definition.revision || !dotty.exists(evt, this.definition.revision)) {
      var err = new Error('Please define an aggregateId!');
      debug(err);
      return callback(err);
    }

    var self = this;

    var revInEvt = dotty.get(evt, this.definition.revision);

    var concatenatedId = this.getConcatenatedId(evt);

    function proceed (revInStore) {
      if (!revInStore && !self.currentHandlingRevisions[concatenatedId] && (self.options.startRevisionNumber === undefined || self.options.startRevisionNumber === null)) {
        self.currentHandlingRevisions[concatenatedId] = revInEvt;
        debug('first revision to store [concatenatedId]=' + concatenatedId + ', [revInStore]=' + (revInStore || 'null') + ', [revInEvt]=' + revInEvt);
        callback(null, function (clb) {
          self.finishGuard(evt, revInStore, clb);
        });
        return;
      }

      if (revInStore && revInEvt < revInStore) {
        debug('event already handled [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
        callback(new AlreadyHandledError('Event: [id]=' + dotty.get(evt, self.definition.id) + ', [revision]=' + revInEvt + ', [concatenatedId]=' + concatenatedId + ' already handled!'), function (clb) {
          clb(null);
        });
        return;
      }

      if (revInStore && revInEvt > revInStore) {
        debug('queue event [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
        self.queueEvent(evt, callback);
        return;
      }

      if (!revInStore && self.options.startRevisionNumber >= 0 && revInEvt > self.options.startRevisionNumber) {
        debug('queue event (startRevisionNumber is set) [concatenatedId]=' + concatenatedId + ', [startRevisionNumber]=' + self.options.startRevisionNumber + ', [revInEvt]=' + revInEvt);
        self.queueEvent(evt, callback);
        return;
      }

      if (!revInStore && revInEvt > self.currentHandlingRevisions[concatenatedId]) {
        debug('queue event [concatenatedId]=' + concatenatedId + ', [currentlyHandling]=' + self.currentHandlingRevisions[concatenatedId] + ', [revInEvt]=' + revInEvt);
        self.queueEvent(evt, callback);
        return;
      }

      if (self.currentHandlingRevisions[concatenatedId] >= revInEvt) {
        debug('event already handling [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
        callback(new AlreadyHandlingError('Event: [id]=' + dotty.get(evt, self.definition.id) + ', [revision]=' + revInEvt + ', [concatenatedId]=' + concatenatedId + ' already handling!'), function (clb) {
          clb(null);
        });
        return;
      }

      self.currentHandlingRevisions[concatenatedId] = revInEvt;

      debug('event is in correct order => go for it! [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
      callback(null, function (clb) {
        self.finishGuard(evt, revInStore, clb);
      });
    }

    function retry (max, loop) {
      setTimeout(function () {
        self.store.get(concatenatedId, function(err, revInStore) {
          if (err) {
            debug(err);
            return callback(err);
          }

          if (loop <= 0) {
            debug('finished loops for retry => proceed [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
            return proceed(revInStore);
          }

          if (!revInStore && revInEvt !== 1) {
            debug('no revision in store => retry [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
            retry(max, --loop);
            return;
          }

          debug('revision in store existing => proceed [concatenatedId]=' + concatenatedId + ', [revInStore]=' + revInStore + ', [revInEvt]=' + revInEvt);
          proceed(revInStore);
        });
      }, randomBetween(max / 5, max));
    }

    process.nextTick(function () {
      self.store.get(concatenatedId, function (err, revInStore) {
        if (err) {
          debug(err);
          return callback(err);
        }

        if (!revInStore && revInEvt !== (self.options.startRevisionNumber || 1) && !self.currentHandlingRevisions[concatenatedId]) {
          var max = (self.options.queueTimeout * self.options.queueTimeoutMaxLoops) / 3;
          max = max < 10 ? 10 : max;
          retry(max, self.options.queueTimeoutMaxLoops);
          return;
        }

        proceed(revInStore);
      });
    });
  }

};

module.exports = RevisionGuard;
<|endoftext|>