<commit_before>import angular from 'angular';
import _ from 'underscore';
import { sum } from '../util';

/**
 * A service for "farming" items by moving them continuously off a character,
 * so that they don't go to the Postmaster.
 */
export function FarmingService($rootScope,
                        $q,
                        dimItemService,
                        dimStoreService,
                        $interval,
                        toaster,
                        dimSettingsService,
                        $i18next,
                        dimBucketService) {
  'ngInject';

  let intervalId;
  let cancelReloadListener;
  const glimmerHashes = new Set([
    269776572, // -house-banners
    3632619276, // -silken-codex
    2904517731, // -axiomatic-beads
    1932910919 // -network-keys
  ]);

  // These are things you may pick up frequently out in the wild
  const makeRoomTypes = dimSettingsService.destinyVersion === 2 ? [
    953998645, // power weapons
    1498876634, // kinetic weapons
    2465295065, // energy weapon
    // are the specific to warlocks? ->
    3448274439, // helmets
    14239492, // chest
    20886954, // boots
    3551918588, // gloves
    1585787867, // class items
  ] : [
    'BUCKET_PRIMARY_WEAPON',
    'BUCKET_SPECIAL_WEAPON',
    'BUCKET_HEAVY_WEAPON',
    'BUCKET_HEAD',
    'BUCKET_ARMS',
    'BUCKET_CHEST',
    'BUCKET_LEGS',
    'BUCKET_CLASS_ITEMS',
    'BUCKET_ARTIFACT',
    'BUCKET_GHOST',
    'BUCKET_CONSUMABLES',
    'BUCKET_MATERIALS'
  ];

  const settings = dimSettingsService.farming;

  const outOfSpaceWarning = _.throttle((store) => {
    toaster.pop('info',
                $i18next.t('FarmingMode.OutOfRoomTitle'),
                $i18next.t('FarmingMode.OutOfRoom', { character: store.name }));
  }, 60000);

  return {
    active: false,
    store: null,
    itemsMoved: 0,
    movingItems: false,
    makingRoom: false,
    // Move all items on the selected character to the vault.
    moveItemsToVault: function(items, incrementCounter) {
      return dimBucketService.getBuckets().then((buckets) => {
        const reservations = {};
        if (settings.makeRoomForItems) {
          // reserve one space in the active character
          reservations[this.store.id] = {};
          makeRoomTypes.forEach((type) => {
            if (dimSettingsService.destinyVersion === 2) {
              reservations[this.store.id][buckets.byHash[type].type] = 1;
            } else {
              reservations[this.store.id][buckets.byId[type].type] = 1;
            }
          });
        }

        return _.reduce(items, (promise, item) => {
          // Move a single item. We do this as a chain of promises so we can reevaluate the situation after each move.
          return promise
            .then(() => {
              const vault = dimStoreService.getVault();
              const vaultSpaceLeft = vault.spaceLeftForItem(item);
              if (vaultSpaceLeft <= 1) {
                // If we're down to one space, try putting it on other characters
                const otherStores = _.select(dimStoreService.getStores(),
                                             (store) => !store.isVault && store.id !== this.store.id);
                const otherStoresWithSpace = _.select(otherStores, (store) => store.spaceLeftForItem(item));

                if (otherStoresWithSpace.length) {
                  if ($featureFlags.debugMoves) {
                    console.log("Farming initiated move:", item.amount, item.name, item.type, 'to', otherStoresWithSpace[0].name, 'from', dimStoreService.getStore(item.owner).name);
                  }
                  return dimItemService.moveTo(item, otherStoresWithSpace[0], false, item.amount, items, reservations);
                }
              }
              if ($featureFlags.debugMoves) {
                console.log("Farming initiated move:", item.amount, item.name, item.type, 'to', vault.name, 'from', dimStoreService.getStore(item.owner).name);
              }
              return dimItemService.moveTo(item, vault, false, item.amount, items, reservations);
            })
            .then(() => {
              if (incrementCounter) {
                this.itemsMoved++;
              }
            })
            .catch((e) => {
              if (e.code === 'no-space') {
                outOfSpaceWarning(this.store);
              } else {
                toaster.pop('error', item.name, e.message);
              }
              throw e;
            });
        }, $q.resolve());
      });
    },
    farmItems: function() {
      const store = dimStoreService.getStore(this.store.id);
      const toMove = _.select(store.items, (i) => {
        return !i.notransfer && (
          i.isEngram() ||
          (i.equipment && i.type === 'Uncommon') ||
          glimmerHashes.has(i.hash));
      });

      if (toMove.length === 0) {
        return $q.resolve();
      }

      this.movingItems = true;
      return this.moveItemsToVault(toMove, true)
        .finally(() => {
          this.movingItems = false;
        });
    },
    // Ensure that there's one open space in each category that could
    // hold an item, so they don't go to the postmaster.
    makeRoomForItems: function() {
      const store = dimStoreService.getStore(this.store.id);

      // If any category is full, we'll move one aside
      const itemsToMove = [];
      makeRoomTypes.forEach((makeRoomType) => {
        const items = store.buckets[makeRoomType];
        if (items.length > 0 && items.length >= store.capacityForItem(items[0])) {
          // We'll move the lowest-value item to the vault.
          const itemToMove = _.min(_.select(items, { equipped: false, notransfer: false }), (i) => {
            let value = {
              Common: 0,
              Uncommon: 1,
              Rare: 2,
              Legendary: 3,
              Exotic: 4
            }[i.tier];
            // And low-stat
            if (i.primStat) {
              value += i.primStat.value / 1000.0;
            }
            return value;
          });
          if (itemToMove !== Infinity) {
            itemsToMove.push(itemToMove);
          }
        }
      });

      if (itemsToMove.length === 0) {
        return $q.resolve();
      }

      this.makingRoom = true;
      return this.moveItemsToVault(itemsToMove, false)
        .finally(() => {
          this.makingRoom = false;
        });
    },
    start: function(store) {
      const self = this;
      function farm() {
        const consolidateHashes = [
          417308266, // three of coins
          211861343, // heavy ammo synth
          928169143, // special ammo synth
          2180254632 // primary ammo synth
        ];

        self.consolidate = _.compact(consolidateHashes.map((hash) => {
          const ret = angular.copy(dimStoreService.getItemAcrossStores({
            hash: hash
          }));
          if (ret) {
            ret.amount = sum(dimStoreService.getStores(), (s) => s.amountOfItem(ret));
          }
          return ret;
        }));

        self.farmItems().then(() => {
          if (settings.makeRoomForItems) {
            self.makeRoomForItems();
          }
        });
      }

      if (!this.active) {
        this.active = true;
        this.store = store;
        this.itemsMoved = 0;
        this.movingItems = false;
        this.makingRoom = false;

        // Whenever the store is reloaded, run the farming algo
        // That way folks can reload manually too
        // TODO: subscribe to stores!
        cancelReloadListener = $rootScope.$on('dim-stores-updated', () => {
          // prevent some recursion...
          if (self.active && !self.movingItems && !self.makingRoom) {
            farm();
          }
        });
        intervalId = $interval(() => {
          // just start reloading stores more often
          dimStoreService.reloadStores();
        }, 60000);
        farm();
      }
    },
    stop: function() {
      if (intervalId) {
        $interval.cancel(intervalId);
      }
      if (cancelReloadListener) {
        cancelReloadListener();
      }
      this.active = false;
    }
  };
}

<commit_msg>farming.service.js doesn't need to handle D2<commit_after>import angular from 'angular';
import _ from 'underscore';
import { sum } from '../util';

/**
 * A service for "farming" items by moving them continuously off a character,
 * so that they don't go to the Postmaster.
 */
export function FarmingService($rootScope,
                        $q,
                        dimItemService,
                        dimStoreService,
                        $interval,
                        toaster,
                        dimSettingsService,
                        $i18next,
                        dimBucketService) {
  'ngInject';

  let intervalId;
  let cancelReloadListener;
  const glimmerHashes = new Set([
    269776572, // -house-banners
    3632619276, // -silken-codex
    2904517731, // -axiomatic-beads
    1932910919 // -network-keys
  ]);

  // These are things you may pick up frequently out in the wild
  const makeRoomTypes = [
    'BUCKET_PRIMARY_WEAPON',
    'BUCKET_SPECIAL_WEAPON',
    'BUCKET_HEAVY_WEAPON',
    'BUCKET_HEAD',
    'BUCKET_ARMS',
    'BUCKET_CHEST',
    'BUCKET_LEGS',
    'BUCKET_CLASS_ITEMS',
    'BUCKET_ARTIFACT',
    'BUCKET_GHOST',
    'BUCKET_CONSUMABLES',
    'BUCKET_MATERIALS'
  ];

  const settings = dimSettingsService.farming;

  const outOfSpaceWarning = _.throttle((store) => {
    toaster.pop('info',
                $i18next.t('FarmingMode.OutOfRoomTitle'),
                $i18next.t('FarmingMode.OutOfRoom', { character: store.name }));
  }, 60000);

  return {
    active: false,
    store: null,
    itemsMoved: 0,
    movingItems: false,
    makingRoom: false,
    // Move all items on the selected character to the vault.
    moveItemsToVault: function(items, incrementCounter) {
      return dimBucketService.getBuckets().then((buckets) => {
        const reservations = {};
        if (settings.makeRoomForItems) {
          // reserve one space in the active character
          reservations[this.store.id] = {};
          makeRoomTypes.forEach((type) => {
            reservations[this.store.id][buckets.byId[type].type] = 1;
          });
        }

        return _.reduce(items, (promise, item) => {
          // Move a single item. We do this as a chain of promises so we can reevaluate the situation after each move.
          return promise
            .then(() => {
              const vault = dimStoreService.getVault();
              const vaultSpaceLeft = vault.spaceLeftForItem(item);
              if (vaultSpaceLeft <= 1) {
                // If we're down to one space, try putting it on other characters
                const otherStores = _.select(dimStoreService.getStores(),
                                             (store) => !store.isVault && store.id !== this.store.id);
                const otherStoresWithSpace = _.select(otherStores, (store) => store.spaceLeftForItem(item));

                if (otherStoresWithSpace.length) {
                  if ($featureFlags.debugMoves) {
                    console.log("Farming initiated move:", item.amount, item.name, item.type, 'to', otherStoresWithSpace[0].name, 'from', dimStoreService.getStore(item.owner).name);
                  }
                  return dimItemService.moveTo(item, otherStoresWithSpace[0], false, item.amount, items, reservations);
                }
              }
              if ($featureFlags.debugMoves) {
                console.log("Farming initiated move:", item.amount, item.name, item.type, 'to', vault.name, 'from', dimStoreService.getStore(item.owner).name);
              }
              return dimItemService.moveTo(item, vault, false, item.amount, items, reservations);
            })
            .then(() => {
              if (incrementCounter) {
                this.itemsMoved++;
              }
            })
            .catch((e) => {
              if (e.code === 'no-space') {
                outOfSpaceWarning(this.store);
              } else {
                toaster.pop('error', item.name, e.message);
              }
              throw e;
            });
        }, $q.resolve());
      });
    },
    farmItems: function() {
      const store = dimStoreService.getStore(this.store.id);
      const toMove = _.select(store.items, (i) => {
        return !i.notransfer && (
          i.isEngram() ||
          (i.equipment && i.type === 'Uncommon') ||
          glimmerHashes.has(i.hash));
      });

      if (toMove.length === 0) {
        return $q.resolve();
      }

      this.movingItems = true;
      return this.moveItemsToVault(toMove, true)
        .finally(() => {
          this.movingItems = false;
        });
    },
    // Ensure that there's one open space in each category that could
    // hold an item, so they don't go to the postmaster.
    makeRoomForItems: function() {
      const store = dimStoreService.getStore(this.store.id);

      // If any category is full, we'll move one aside
      const itemsToMove = [];
      makeRoomTypes.forEach((makeRoomType) => {
        const items = store.buckets[makeRoomType];
        if (items.length > 0 && items.length >= store.capacityForItem(items[0])) {
          // We'll move the lowest-value item to the vault.
          const itemToMove = _.min(_.select(items, { equipped: false, notransfer: false }), (i) => {
            let value = {
              Common: 0,
              Uncommon: 1,
              Rare: 2,
              Legendary: 3,
              Exotic: 4
            }[i.tier];
            // And low-stat
            if (i.primStat) {
              value += i.primStat.value / 1000.0;
            }
            return value;
          });
          if (itemToMove !== Infinity) {
            itemsToMove.push(itemToMove);
          }
        }
      });

      if (itemsToMove.length === 0) {
        return $q.resolve();
      }

      this.makingRoom = true;
      return this.moveItemsToVault(itemsToMove, false)
        .finally(() => {
          this.makingRoom = false;
        });
    },
    start: function(store) {
      const self = this;
      function farm() {
        const consolidateHashes = [
          417308266, // three of coins
          211861343, // heavy ammo synth
          928169143, // special ammo synth
          2180254632 // primary ammo synth
        ];

        self.consolidate = _.compact(consolidateHashes.map((hash) => {
          const ret = angular.copy(dimStoreService.getItemAcrossStores({
            hash: hash
          }));
          if (ret) {
            ret.amount = sum(dimStoreService.getStores(), (s) => s.amountOfItem(ret));
          }
          return ret;
        }));

        self.farmItems().then(() => {
          if (settings.makeRoomForItems) {
            self.makeRoomForItems();
          }
        });
      }

      if (!this.active) {
        this.active = true;
        this.store = store;
        this.itemsMoved = 0;
        this.movingItems = false;
        this.makingRoom = false;

        // Whenever the store is reloaded, run the farming algo
        // That way folks can reload manually too
        // TODO: subscribe to stores!
        cancelReloadListener = $rootScope.$on('dim-stores-updated', () => {
          // prevent some recursion...
          if (self.active && !self.movingItems && !self.makingRoom) {
            farm();
          }
        });
        intervalId = $interval(() => {
          // just start reloading stores more often
          dimStoreService.reloadStores();
        }, 60000);
        farm();
      }
    },
    stop: function() {
      if (intervalId) {
        $interval.cancel(intervalId);
      }
      if (cancelReloadListener) {
        cancelReloadListener();
      }
      this.active = false;
    }
  };
}

<|endoftext|>


<commit_before>/*
 * Copyright 2014-2015 Fabian Tollenaar <fabian@starting-point.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

module.exports = function(app) {
  'use strict';

  var debug = require('debug')('signalk-server:interfaces:rest');
  var express = require('express');

  var pathPrefix = '/signalk';
  var versionPrefix = '/v1';
  var apiPathPrefix = pathPrefix + versionPrefix + '/api/';
  var streamPath = pathPrefix + versionPrefix + '/stream';

  return {
    start: function() {
      app.use('/', express.static(__dirname + '/../../public'));

      app.get(apiPathPrefix + '*', function(req, res, next) {
        var path = String(req.path).replace(apiPathPrefix, '');
        var data = app.signalk.retrieve();
        var last = data;

        if (path === 'self') {
          return res.json(app.selfId)
        }

        path = path.length > 0 ? path.replace(/\/$/, '').replace(/self/, app.selfId).split('/') : [];

        for(var i in path) {
          var p = path[i];

          if(typeof last[p] !== 'undefined') {
            last = last[p];
          } else {
            next()
            return
          }
        }

        return res.json(last);
      });

      app.get(pathPrefix, function(req, res) {
        var host = req.headers.host
        var splitHost = host.split(':')

        var httpProtocol = 'http://';
        var wsProtocol = 'ws://';
        if (app.config.settings.ssl) {
          httpProtocol = 'https://';
          wsProtocol = 'wss://';
        }

        const services = {
          'version': '1.alpha1',
          'signalk-http': httpProtocol + host  + apiPathPrefix,
          'signalk-ws': wsProtocol + host + streamPath
        }

        if (app.interfaces.tcp && app.interfaces.tcp.data) {
          services['signalk-tcp'] = 'tcp://' + splitHost[0] + app.interfaces.tcp.data.port
        }

        res.json({
          'endpoints': {
            'v1': services
          }
        });
      })

    },

    mdns: {
      name: app.config.settings.ssl ? "_signalk-https" : "_signalk-http",
      type: "tcp",
      port: app.config.port
    }
  };
};
<commit_msg>feature: provide server information in the result from /signalk (#241)<commit_after>/*
 * Copyright 2014-2015 Fabian Tollenaar <fabian@starting-point.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

module.exports = function(app) {
  'use strict';

  var debug = require('debug')('signalk-server:interfaces:rest');
  var express = require('express');

  var pathPrefix = '/signalk';
  var versionPrefix = '/v1';
  var apiPathPrefix = pathPrefix + versionPrefix + '/api/';
  var streamPath = pathPrefix + versionPrefix + '/stream';

  return {
    start: function() {
      app.use('/', express.static(__dirname + '/../../public'));

      app.get(apiPathPrefix + '*', function(req, res, next) {
        var path = String(req.path).replace(apiPathPrefix, '');
        var data = app.signalk.retrieve();
        var last = data;

        if (path === 'self') {
          return res.json(app.selfId)
        }

        path = path.length > 0 ? path.replace(/\/$/, '').replace(/self/, app.selfId).split('/') : [];

        for(var i in path) {
          var p = path[i];

          if(typeof last[p] !== 'undefined') {
            last = last[p];
          } else {
            next()
            return
          }
        }

        return res.json(last);
      });

      app.get(pathPrefix, function(req, res) {
        var host = req.headers.host
        var splitHost = host.split(':')

        var httpProtocol = 'http://';
        var wsProtocol = 'ws://';
        if (app.config.settings.ssl) {
          httpProtocol = 'https://';
          wsProtocol = 'wss://';
        }

        const services = {
          'version': '1.alpha1',
          'signalk-http': httpProtocol + host  + apiPathPrefix,
          'signalk-ws': wsProtocol + host + streamPath
        }

        if (app.interfaces.tcp && app.interfaces.tcp.data) {
          services['signalk-tcp'] = 'tcp://' + splitHost[0] + app.interfaces.tcp.data.port
        }

        res.json({
          'endpoints': {
            'v1': services
          },
          'server': {
            id: app.config.name,
            version: app.config.version
          }
        });
      })

    },

    mdns: {
      name: app.config.settings.ssl ? "_signalk-https" : "_signalk-http",
      type: "tcp",
      port: app.config.port
    }
  };
};
<|endoftext|>


<commit_before>/*
 * Copyright 2014-2015 Fabian Tollenaar <fabian@starting-point.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

module.exports = function(app) {
  'use strict';

  var debug = require('debug')('signalk-server:interfaces:rest');
  var express = require('express');

  var pathPrefix = '/signalk';
  var versionPrefix = '/v1';
  var apiPathPrefix = pathPrefix + versionPrefix + '/api/';
  var streamPath = pathPrefix + versionPrefix + '/stream';

  return {
    start: function() {
      app.use('/', express.static(__dirname + '/../../public'));

      app.get(apiPathPrefix + '*', function(req, res) {
        var path = String(req.path).replace(apiPathPrefix, '');
        var data = app.signalk.retrieve();
        var last = data;

        if (path === 'self') {
          return res.json(app.selfId)
        }

        path = path.length > 0 ? path.replace(/\/$/, '').replace(/self/, app.selfId).split('/') : [];

        for(var i in path) {
          var p = path[i];

          if(typeof last[p] !== 'undefined') {
            last = last[p];
          } else {
            res.status(404).send("Not found");
            return
          }
        }

        return res.json(last);
      });

      app.get(pathPrefix, function(req, res) {
        var host = app.config.getExternalHostname();
        var port = app.config.getExternalPort();
        port = (port === '' || port === 80) ? '' : ':' + port;

        var httpProtocol = 'http://';
        var wsProtocol = 'ws://';
        if (app.config.settings.ssl) {
          httpProtocol = 'https://';
          wsProtocol = 'wss://';
        }

        res.json({
          'endpoints': {
            'v1': {
              'version': '1.alpha1',
              'signalk-http': httpProtocol + host + port + apiPathPrefix,
              'signalk-ws': wsProtocol + host + port + streamPath,
              'signalk-tcp': 'tcp://' + host + ':3858'
            }
          }
        });
      })

    },

    mdns: {
      name: app.config.settings.ssl ? "_signalk-https" : "_signalk-http",
      type: "tcp",
      port: app.config.port
    }
  };
};
<commit_msg>fix: use host & port from request in /signalk<commit_after>/*
 * Copyright 2014-2015 Fabian Tollenaar <fabian@starting-point.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

module.exports = function(app) {
  'use strict';

  var debug = require('debug')('signalk-server:interfaces:rest');
  var express = require('express');

  var pathPrefix = '/signalk';
  var versionPrefix = '/v1';
  var apiPathPrefix = pathPrefix + versionPrefix + '/api/';
  var streamPath = pathPrefix + versionPrefix + '/stream';

  return {
    start: function() {
      app.use('/', express.static(__dirname + '/../../public'));

      app.get(apiPathPrefix + '*', function(req, res) {
        var path = String(req.path).replace(apiPathPrefix, '');
        var data = app.signalk.retrieve();
        var last = data;

        if (path === 'self') {
          return res.json(app.selfId)
        }

        path = path.length > 0 ? path.replace(/\/$/, '').replace(/self/, app.selfId).split('/') : [];

        for(var i in path) {
          var p = path[i];

          if(typeof last[p] !== 'undefined') {
            last = last[p];
          } else {
            res.status(404).send("Not found");
            return
          }
        }

        return res.json(last);
      });

      app.get(pathPrefix, function(req, res) {
        var host = req.headers.host
        var splitHost = host.split(':')

        var httpProtocol = 'http://';
        var wsProtocol = 'ws://';
        if (app.config.settings.ssl) {
          httpProtocol = 'https://';
          wsProtocol = 'wss://';
        }

        res.json({
          'endpoints': {
            'v1': {
              'version': '1.alpha1',
              'signalk-http': httpProtocol + host  + apiPathPrefix,
              'signalk-ws': wsProtocol + host + streamPath,
              'signalk-tcp': 'tcp://' + splitHost[0] + ':3858'
            }
          }
        });
      })

    },

    mdns: {
      name: app.config.settings.ssl ? "_signalk-https" : "_signalk-http",
      type: "tcp",
      port: app.config.port
    }
  };
};
<|endoftext|>


<commit_before>var async = require('async');
var CheckEvent = require('../models/checkEvent');
var Check = require('../models/check');

var PAUSED = -1;
var DOWN = 0;
var UP = 1;

/*
 * Usage:
 *   var builder = new IntervalBuilder();
 *   builder.addTarget(check1);
 *   builder.addTarget(check2);
 *   builder.build(begin, end, function(err, intervals, downtime) {
 *     // do things
 *   });
 */
var IntervalBuilder = function() {
  this.objectIds = [];
  this.nbObjects = 0;
  this.states = {};
  this.intervals = []; // intervals are [begin, end, state]
  this.currentState = null;
  this.currentInterval = [];
  this.downtime = 0;
  this.duration = 0;
};

IntervalBuilder.prototype.addTarget = function(objectId) {
  this.objectIds.push(objectId._id ? objectId._id : objectId);
  this.nbObjects++;
};

IntervalBuilder.prototype.build = function(begin, end, callback) {
  var self = this;

  async.auto({
    initialState: function(next) {
        self.determineInitialState(begin, next);
    },
    interval: ['initialState', function(next) {
        self.buildIntervalsForPeriod(begin, end, next);
    }],
    check: ['initialState', 'interval', function (next) {
        self.getCurrentCheck(next)
    }],
    duration: ['initialState', 'interval', 'check', function (next, results) {
        self.calculateDuration(results.check, begin, end, next);
    }],
    downtime: ['initialState', 'interval', 'check', 'duration', function (next, results) {
        self.calculateDowntime(results.check, begin, end, next);
    }]
    }, function(err, results) {
      if (err) {
        return callback(err);
      }

      self.duration = results.duration;
      self.downtime = results.downtime;

      return callback(null, self.intervals, self.downtime, self.duration);
  });
};

IntervalBuilder.prototype.getCurrentCheck = function(callback) {
  Check.findOne({ _id: this.objectIds[0] }, callback);
};

IntervalBuilder.prototype.isMultiTarget = function() {
  return this.nbObjects > 1;
};

IntervalBuilder.prototype.determineInitialState = function(timestamp, callback) {
  var self = this;
  async.forEach(this.objectIds, function(objectId, next) {
    CheckEvent.find()
    .where('check').equals(objectId)
    .where('timestamp').lte(timestamp)
    .sort({ timestamp: -1 })
    .findOne(function(err, checkEvent) {
      if (err) return next(err);
      if (!checkEvent) {
        // No ping ever - start the period as paused
        self.addEvent(objectId, 'paused', timestamp);
        return next();
      }
      self.addEvent(objectId, checkEvent.message);
      next();
    });
  }, function(err) {
    if (err) return callback(err);
    self.updateCurrentState(timestamp);
    return callback(null);
  });
};

IntervalBuilder.prototype.calculateDuration = function(check, begin, end, callback) {
  var durationBegin = Math.max(begin, check.firstTested),
    durationEnd = Math.min(end, check.lastTested),
    duration = durationEnd - durationBegin;

  if (this.isMultiTarget()) {
    // it's a tag - no other way
    return callback(null, duration);
  }

  this.intervals.forEach(function(interval) {
    if (interval[2] != PAUSED || interval[1] < durationBegin || interval[0] > durationEnd) {
      return;
    }

    interval[0] = Math.max(durationBegin, interval[0]);
    interval[1] = Math.min(durationEnd, interval[1]);

    duration -= interval[1] - interval[0];
  });

  return callback(null, duration);
};

IntervalBuilder.prototype.calculateDowntime = function(check, begin, end, callback) {
  var durationBegin = Math.max(begin, check.firstTested);
  var durationEnd = Math.min(end, check.lastTested);

  if (this.isMultiTarget()) {
    // it's a tag - no other way
    return callback(null, this.currentState == DOWN ? durationEnd - durationBegin : 0);
  }

  var downtime = 0;
  var currentIntervalEnd = null;
  this.intervals.forEach(function(interval) {
    currentIntervalEnd = interval[1];
    if (interval[2] != DOWN || interval[1] < durationBegin || interval[0] > durationEnd) {
      return true; // will act as a continue
    }

    interval[0] = Math.max(durationBegin, interval[0]);
    interval[1] = Math.min(durationEnd, interval[1]);

    downtime += interval[1] - interval[0];
  });

  if (!check.isUp) {
    if(currentIntervalEnd === null) {
      downtime += durationEnd - durationBegin;
    }else {
      downtime += durationEnd - currentIntervalEnd;
    }
  }

  return callback(null, downtime);
};

IntervalBuilder.prototype.buildIntervalsForPeriod = function(begin, end, callback) {
  var self = this;
  CheckEvent.find()
  .where('check').in(this.objectIds)
  .where('timestamp').gt(begin).lte(end)
  .sort({ timestamp: 1 })
  .find(function(err, checkEvents) {
    if (err) return callback(err);
    checkEvents.forEach(function(checkEvent) {
      if (self.addEvent(checkEvent.check, checkEvent.message)) {
        self.updateCurrentState(checkEvent.timestamp);
      }
    });
    callback(null);
  });
};

/**
 * Add an event for a given check.
 *
 * Returns true if the event modifies the state of a given check, false otherwise
 */
IntervalBuilder.prototype.addEvent = function(objectId, message) {
  switch (message) {
    case 'up':
      if (!this.isUp(objectId)) {
        this.states[objectId] = UP;
        return true;
      }
      break;
    case 'down':
      if (!this.isDown(objectId)) {
        this.states[objectId] = DOWN;
        return true;
      }
      break;
    case 'paused':
    case 'restarted':
    default:
      if (!this.isPaused(objectId)) {
        this.states[objectId] = PAUSED;
        return true;
      }
      break;
  }
  return false;
};

/**
 * Return true if the global state was updated, false otherwise.
 */
IntervalBuilder.prototype.updateCurrentState = function(timestamp) {
  timestamp = this.getTimestamp(timestamp);
  var currentState = this.getGlobalState();
  if (currentState !== this.currentState) {
    this.completeCurrentInterval(timestamp);
    this.currentInterval = currentState == 1 ? [] : [timestamp];
    this.currentState = currentState;
    return true;
  }
  return false;
};

IntervalBuilder.prototype.getGlobalState = function() {
  var ups = 0;
  var paused = 0;
  for (var objectId in this.states) {
    if (this.isUp(objectId)) ups++;
    if (this.isPaused(objectId)) paused++;
  }
  if (!this.isMultiTarget() && paused > 0) {
    return PAUSED; // global state is paused because there is only one check
  }
  if ((ups + paused) == this.nbObjects) {
    return UP; // ignore paused in multiTarget
  }
  return DOWN; // at least one of the targets is paused
};

IntervalBuilder.prototype.isUp = function(objectId) {
  return this.states[objectId] == UP;
};

IntervalBuilder.prototype.isDown = function(objectId) {
  return this.states[objectId] == DOWN;
};

IntervalBuilder.prototype.isPaused = function(objectId) {
  return this.states[objectId] == PAUSED;
};

IntervalBuilder.prototype.getTimestamp = function(date) {
  return date.valueOf ? date.valueOf() : date;
};

IntervalBuilder.prototype.completeCurrentInterval = function(timestamp) {
  if (this.currentInterval.length != 1) {
    // no current interval - ignore
    return false;
  }
  if (this.currentState == DOWN) {
    this.downtime += timestamp - this.currentInterval[0];
  }

  this.intervals.push(this.currentInterval.concat([timestamp, this.currentState]));
  return true;
};

module.exports = IntervalBuilder;
<commit_msg>added fix mentioned in https://github.com/fzaninotto/uptime/pull/246/files<commit_after>var async = require('async');
var CheckEvent = require('../models/checkEvent');
var Check = require('../models/check');

var PAUSED = -1;
var DOWN = 0;
var UP = 1;

/*
 * Usage:
 *   var builder = new IntervalBuilder();
 *   builder.addTarget(check1);
 *   builder.addTarget(check2);
 *   builder.build(begin, end, function(err, intervals, downtime) {
 *     // do things
 *   });
 */
var IntervalBuilder = function() {
  this.objectIds = [];
  this.nbObjects = 0;
  this.states = {};
  this.intervals = []; // intervals are [begin, end, state]
  this.currentState = null;
  this.currentInterval = [];
  this.downtime = 0;
  this.duration = 0;
};

IntervalBuilder.prototype.addTarget = function(objectId) {
  this.objectIds.push(objectId._id ? objectId._id : objectId);
  this.nbObjects++;
};

IntervalBuilder.prototype.build = function(begin, end, callback) {
  var self = this;

  async.auto({
    initialState: function(next) {
        self.determineInitialState(begin, next);
    },
    interval: ['initialState', function(next) {
        self.buildIntervalsForPeriod(begin, end, next);
    }],
    check: ['initialState', 'interval', function (next) {
        self.getCurrentCheck(next)
    }],
    duration: ['initialState', 'interval', 'check', function (next, results) {
        if (typeof results.check !== 'undefined' && results.check !== null) {
          self.calculateDuration(results.check, begin, end, next);
        }
    }],
    downtime: ['initialState', 'interval', 'check', 'duration', function (next, results) {
        if (typeof results.check !== 'undefined' && results.check !== null) {
          self.calculateDowntime(results.check, begin, end, next);
        };
    }]
    }, function(err, results) {
      if (err) {
        return callback(err);
      }

      self.duration = results.duration;
      self.downtime = results.downtime;

      return callback(null, self.intervals, self.downtime, self.duration);
  });
};

IntervalBuilder.prototype.getCurrentCheck = function(callback) {
  Check.findOne({ _id: this.objectIds[0] }, callback);
};

IntervalBuilder.prototype.isMultiTarget = function() {
  return this.nbObjects > 1;
};

IntervalBuilder.prototype.determineInitialState = function(timestamp, callback) {
  var self = this;
  async.forEach(this.objectIds, function(objectId, next) {
    CheckEvent.find()
    .where('check').equals(objectId)
    .where('timestamp').lte(timestamp)
    .sort({ timestamp: -1 })
    .findOne(function(err, checkEvent) {
      if (err) return next(err);
      if (!checkEvent) {
        // No ping ever - start the period as paused
        self.addEvent(objectId, 'paused', timestamp);
        return next();
      }
      self.addEvent(objectId, checkEvent.message);
      next();
    });
  }, function(err) {
    if (err) return callback(err);
    self.updateCurrentState(timestamp);
    return callback(null);
  });
};

IntervalBuilder.prototype.calculateDuration = function(check, begin, end, callback) {
  var durationBegin = Math.max(begin, check.firstTested),
    durationEnd = Math.min(end, check.lastTested),
    duration = durationEnd - durationBegin;

  if (this.isMultiTarget()) {
    // it's a tag - no other way
    return callback(null, duration);
  }

  this.intervals.forEach(function(interval) {
    if (interval[2] != PAUSED || interval[1] < durationBegin || interval[0] > durationEnd) {
      return;
    }

    interval[0] = Math.max(durationBegin, interval[0]);
    interval[1] = Math.min(durationEnd, interval[1]);

    duration -= interval[1] - interval[0];
  });

  return callback(null, duration);
};

IntervalBuilder.prototype.calculateDowntime = function(check, begin, end, callback) {
  var durationBegin = Math.max(begin, check.firstTested);
  var durationEnd = Math.min(end, check.lastTested);

  if (this.isMultiTarget()) {
    // it's a tag - no other way
    return callback(null, this.currentState == DOWN ? durationEnd - durationBegin : 0);
  }

  var downtime = 0;
  var currentIntervalEnd = null;
  this.intervals.forEach(function(interval) {
    currentIntervalEnd = interval[1];
    if (interval[2] != DOWN || interval[1] < durationBegin || interval[0] > durationEnd) {
      return true; // will act as a continue
    }

    interval[0] = Math.max(durationBegin, interval[0]);
    interval[1] = Math.min(durationEnd, interval[1]);

    downtime += interval[1] - interval[0];
  });

  if (!check.isUp) {
    if(currentIntervalEnd === null) {
      downtime += durationEnd - durationBegin;
    }else {
      downtime += durationEnd - currentIntervalEnd;
    }
  }

  return callback(null, downtime);
};

IntervalBuilder.prototype.buildIntervalsForPeriod = function(begin, end, callback) {
  var self = this;
  CheckEvent.find()
  .where('check').in(this.objectIds)
  .where('timestamp').gt(begin).lte(end)
  .sort({ timestamp: 1 })
  .find(function(err, checkEvents) {
    if (err) return callback(err);
    checkEvents.forEach(function(checkEvent) {
      if (self.addEvent(checkEvent.check, checkEvent.message)) {
        self.updateCurrentState(checkEvent.timestamp);
      }
    });
    callback(null);
  });
};

/**
 * Add an event for a given check.
 *
 * Returns true if the event modifies the state of a given check, false otherwise
 */
IntervalBuilder.prototype.addEvent = function(objectId, message) {
  switch (message) {
    case 'up':
      if (!this.isUp(objectId)) {
        this.states[objectId] = UP;
        return true;
      }
      break;
    case 'down':
      if (!this.isDown(objectId)) {
        this.states[objectId] = DOWN;
        return true;
      }
      break;
    case 'paused':
    case 'restarted':
    default:
      if (!this.isPaused(objectId)) {
        this.states[objectId] = PAUSED;
        return true;
      }
      break;
  }
  return false;
};

/**
 * Return true if the global state was updated, false otherwise.
 */
IntervalBuilder.prototype.updateCurrentState = function(timestamp) {
  timestamp = this.getTimestamp(timestamp);
  var currentState = this.getGlobalState();
  if (currentState !== this.currentState) {
    this.completeCurrentInterval(timestamp);
    this.currentInterval = currentState == 1 ? [] : [timestamp];
    this.currentState = currentState;
    return true;
  }
  return false;
};

IntervalBuilder.prototype.getGlobalState = function() {
  var ups = 0;
  var paused = 0;
  for (var objectId in this.states) {
    if (this.isUp(objectId)) ups++;
    if (this.isPaused(objectId)) paused++;
  }
  if (!this.isMultiTarget() && paused > 0) {
    return PAUSED; // global state is paused because there is only one check
  }
  if ((ups + paused) == this.nbObjects) {
    return UP; // ignore paused in multiTarget
  }
  return DOWN; // at least one of the targets is paused
};

IntervalBuilder.prototype.isUp = function(objectId) {
  return this.states[objectId] == UP;
};

IntervalBuilder.prototype.isDown = function(objectId) {
  return this.states[objectId] == DOWN;
};

IntervalBuilder.prototype.isPaused = function(objectId) {
  return this.states[objectId] == PAUSED;
};

IntervalBuilder.prototype.getTimestamp = function(date) {
  return date.valueOf ? date.valueOf() : date;
};

IntervalBuilder.prototype.completeCurrentInterval = function(timestamp) {
  if (this.currentInterval.length != 1) {
    // no current interval - ignore
    return false;
  }
  if (this.currentState == DOWN) {
    this.downtime += timestamp - this.currentInterval[0];
  }

  this.intervals.push(this.currentInterval.concat([timestamp, this.currentState]));
  return true;
};

module.exports = IntervalBuilder;
<|endoftext|>


<commit_before>;(function($, window, document, undefined) {

  var DraggableGridList = function(element, options) {
    this.options = $.extend({}, this.defaults, options);
    this.$element = $(element);
    this.init();
  };

  DraggableGridList.prototype.defaults = {
    rows: 5,
    widthHeightRatio: 1
  };

  DraggableGridList.prototype.init = function() {
    // Read items and their meta data
    this.$items = this.$element.children('li');
    this.items = this._generateItemsFromDOM();

    this._initGridList();
    this.render();

    // Init Draggable JQuery UI plugin for each of the list items
    // http://api.jqueryui.com/draggable/
    this.$items.draggable({
      zIndex: this.items.length,
      scroll: false
    });
    this._bindDragEvents();

    // Used to highlight a position an element will land on upon drop
    this.$positionHighlight = this.$element.find('.position-highlight').hide();
  };

  DraggableGridList.prototype.destroy = function() {
    this._unbindDragEvents();
  };

  DraggableGridList.prototype.render = function() {
    // Render the list for the first time
    this._calculateCellSize();
    this._applySizeToItems();
    this._applyPositionToItems();
  };

  DraggableGridList.prototype.resize = function(rows) {
    if (rows) {
      this.options.rows = rows;
    }
    this._initGridList();
    this.render();
  };

  DraggableGridList.prototype._initGridList = function() {
    // Create instance of GridList (decoupled lib for handling the grid
    // positioning and sorting post-drag and dropping)
    this.gridList = new GridList(this.items, {rows: this.options.rows});

    // Create a deep copy of the items; we use them to revert the item
    // positions after each drag change, making an entire drag operation less
    // distructable
    this._items = GridList.cloneItems(this.items);
  };

  DraggableGridList.prototype._bindDragEvents = function() {
    this._onStart = this._bindMethod(this._onStart);
    this._onDrag = this._bindMethod(this._onDrag);
    this._onStop = this._bindMethod(this._onStop);
    this.$items.on('dragstart', this._onStart);
    this.$items.on('drag', this._onDrag);
    this.$items.on('dragstop', this._onStop);
  };

  DraggableGridList.prototype._unbindDragEvents = function() {
    this.$items.off('dragstart', this._onStart);
    this.$items.off('drag', this._onDrag);
    this.$items.off('dragstop', this._onStop);
  };

  DraggableGridList.prototype._onStart = function(event, ui) {
    // Since dragging actually alters the grid, we need to establish the number
    // of cols (+1 extra) before the drag starts
    this._maxGridCols = this.gridList.grid.length;
  };

  DraggableGridList.prototype._onDrag = function(event, ui) {
    var item = this._getItemByElement(ui.helper),
        newPosition = this._snapItemPositionToGrid(item);

    if (this._dragPositionChanged(newPosition)) {
      this._previousDragPosition = newPosition;

      // Regenerate the grid with the positions from when the drag started
      this._swapItems(this._items, this.items);
      this.gridList.rebuildGrid();

      // Since the items list is a deep copy, we need to fetch the item
      // corresponding to this drag action again
      item = this._getItemByElement(ui.helper);
      this.gridList.moveItemToPosition(item, newPosition);

      // Visually update item positions and highlight shape
      this._applyPositionToItems();
      this._highlightPositionForItem(item);
    }
  };

  DraggableGridList.prototype._onStop = function(event, ui) {
    // Use the current items as the next starting point for a new drag action
    this._swapItems(this.items, this._items);
    this._previousDragPosition = null;

    this._applyPositionToItems();
    this._removePositionHighlight();
  };

  DraggableGridList.prototype._bindMethod = function(fn) {
    /**
     * Bind prototype method to instance scope (similar to CoffeeScript's fat
     * arrow)
     */
    var that = this;
    return function() {
      return fn.apply(that, arguments);
    };
  };

  DraggableGridList.prototype._generateItemsFromDOM = function() {
    /**
     * Generate the structure of items used by the GridList lib, using the DOM
     * data of the children of the targeted element. The items will have an
     * additional reference to the initial DOM element attached, in order to
     * trace back to it and re-render it once its properties are changed by the
     * GridList lib
     */
    var _this = this,
        items = [],
        item;
    this.$items.each(function(i, element) {
      item = {
        $element: $(element)
      };
      $.extend(item, $(element).data());
      items.push(item);
    });
    return items;
  };

  DraggableGridList.prototype._getItemByElement = function(element) {
    // XXX: this could be optimized by storing the item reference inside the
    // meta data of the DOM element
    for (var i = 0; i < this.items.length; i++) {
      if (this.items[i].$element.is(element)) {
        return this.items[i];
      }
    }
  };

  DraggableGridList.prototype._snapItemPositionToGrid = function(item) {
    var position = item.$element.position(),
        row,
        col;
    position[0] -= this.$element.position().left;
    col = Math.round(position.left / this._cellWidth);
    row = Math.round(position.top / this._cellHeight);
    // Keep item position within the grid and don't let the item create more
    // than one extra column
    col = Math.max(col, 0);
    row = Math.max(row, 0);
    col = Math.min(col, this._maxGridCols);
    row = Math.min(row, this.options.rows - item.h);
    return [col, row];
  };

  DraggableGridList.prototype._dragPositionChanged = function(newPosition) {
    if (!this._previousDragPosition) {
      return true;
    }
    return (newPosition[0] != this._previousDragPosition[0] ||
            newPosition[1] != this._previousDragPosition[1]);
  };

  DraggableGridList.prototype._highlightPositionForItem = function(item) {
    this.$positionHighlight.css({
      width: this._getItemWidth(item),
      height: this._getItemHeight(item),
      left: item.x * this._cellWidth,
      top: item.y * this._cellHeight
    }).show();
  };

  DraggableGridList.prototype._removePositionHighlight = function() {
    this.$positionHighlight.hide();
  };

  DraggableGridList.prototype._calculateCellSize = function() {
    this._cellHeight = this.$element.height() / this.options.rows;
    this._cellWidth = this._cellHeight * this.options.widthHeightRatio;
    if (this.options.heightToFontSizeRatio) {
      this._fontSize = this._cellHeight * this.options.heightToFontSizeRatio;
    }
  };

  DraggableGridList.prototype._applySizeToItems = function() {
    for (var i = 0; i < this.items.length; i++) {
      this.items[i].$element.css({
        width: this._getItemWidth(this.items[i]),
        height: this._getItemHeight(this.items[i])
      });
    }
    if (this.options.heightToFontSizeRatio) {
      this.$items.css('font-size', this._fontSize);
    }
  };

  DraggableGridList.prototype._applyPositionToItems = function() {
    // TODO: Implement group separators
    for (var i = 0; i < this.items.length; i++) {
      // Don't interfere with the positions of the dragged items
      if (this.items[i].move) {
        continue;
      }
      this.items[i].$element.css({
        left: this.items[i].x * this._cellWidth,
        top: this.items[i].y * this._cellHeight
      });
    }
    // Update the width of the entire grid container with an extra column on
    // the right for extra dragging room
    this.$element.width((this.gridList.grid.length + 1) * this._cellWidth);
  };

  DraggableGridList.prototype._getItemWidth = function(item) {
    return item.w * this._cellWidth;
  };

  DraggableGridList.prototype._getItemHeight = function(item) {
    return item.h * this._cellHeight;
  };

  DraggableGridList.prototype._swapItems = function(from, to) {
    /**
     * We use the same two arrays to not constantly create new objects with
     * leaking potential. We assume the same number of items in both!
     */
    for (var i = 0; i < from.length; i++) {
      to[i] = $.extend({}, from[i]);
    }
  };

  $.fn.gridList = function(options) {
    if (!window.GridList) {
      throw new Error('GridList lib required');
    }
    var instance,
        method,
        args;
    if (typeof(options) == 'string') {
      method = options;
      args =  Array.prototype.slice.call(arguments, 1);
    }
    this.each(function() {
      instance = $(this).data('_gridList');
      if (!instance) {
        instance = new DraggableGridList(this, options);
        $(this).data('_gridList', instance);
      }
      if (method) {
        instance[method].apply(instance, args);
      }
    });
    // Maintain jQuery chain
    return this;
  };

})(jQuery, window, document);
<commit_msg>Move and rename methods around<commit_after>;(function($, window, document, undefined) {

  var DraggableGridList = function(element, options) {
    this.options = $.extend({}, this.defaults, options);
    this.$element = $(element);
    this._init();
    this._bindEvents();
  };

  DraggableGridList.prototype.defaults = {
    rows: 5,
    widthHeightRatio: 1
  };

  DraggableGridList.prototype.destroy = function() {
    this._unbindEvents();
  };

  DraggableGridList.prototype.resize = function(rows) {
    if (rows) {
      this.options.rows = rows;
    }
    this._initGridList();
    this.render();
  };

  DraggableGridList.prototype.render = function() {
    this._calculateCellSize();
    this._applySizeToItems();
    this._applyPositionToItems();
  };

  DraggableGridList.prototype._bindMethod = function(fn) {
    /**
     * Bind prototype method to instance scope (similar to CoffeeScript's fat
     * arrow)
     */
    var that = this;
    return function() {
      return fn.apply(that, arguments);
    };
  };

  DraggableGridList.prototype._init = function() {
    // Read items and their meta data
    this.$items = this.$element.children('li');
    this.items = this._generateItemsFromDOM();
    // Used to highlight a position an element will land on upon drop
    this.$positionHighlight = this.$element.find('.position-highlight').hide();

    this._initGridList();
    this.render();

    // Init Draggable JQuery UI plugin for each of the list items
    // http://api.jqueryui.com/draggable/
    this.$items.draggable({
      zIndex: this.items.length,
      scroll: false
    });
  };

  DraggableGridList.prototype._initGridList = function() {
    // Create instance of GridList (decoupled lib for handling the grid
    // positioning and sorting post-drag and dropping)
    this.gridList = new GridList(this.items, {rows: this.options.rows});

    // Create a deep copy of the items; we use them to revert the item
    // positions after each drag change, making an entire drag operation less
    // distructable
    this._items = GridList.cloneItems(this.items);
  };

  DraggableGridList.prototype._bindEvents = function() {
    this._onStart = this._bindMethod(this._onStart);
    this._onDrag = this._bindMethod(this._onDrag);
    this._onStop = this._bindMethod(this._onStop);
    this.$items.on('dragstart', this._onStart);
    this.$items.on('drag', this._onDrag);
    this.$items.on('dragstop', this._onStop);
  };

  DraggableGridList.prototype._unbindEvents = function() {
    this.$items.off('dragstart', this._onStart);
    this.$items.off('drag', this._onDrag);
    this.$items.off('dragstop', this._onStop);
  };

  DraggableGridList.prototype._onStart = function(event, ui) {
    // Since dragging actually alters the grid, we need to establish the number
    // of cols (+1 extra) before the drag starts
    this._maxGridCols = this.gridList.grid.length;
  };

  DraggableGridList.prototype._onDrag = function(event, ui) {
    var item = this._getItemByElement(ui.helper),
        newPosition = this._snapItemPositionToGrid(item);

    if (this._dragPositionChanged(newPosition)) {
      this._previousDragPosition = newPosition;

      // Regenerate the grid with the positions from when the drag started
      _swapItems(this._items, this.items);
      this.gridList.rebuildGrid();

      // Since the items list is a deep copy, we need to fetch the item
      // corresponding to this drag action again
      item = this._getItemByElement(ui.helper);
      this.gridList.moveItemToPosition(item, newPosition);

      // Visually update item positions and highlight shape
      this._applyPositionToItems();
      this._highlightPositionForItem(item);
    }
  };

  DraggableGridList.prototype._onStop = function(event, ui) {
    // Use the current items as the next starting point for a new drag action
    _swapItems(this.items, this._items);
    this._previousDragPosition = null;

    this._applyPositionToItems();
    this._removePositionHighlight();
  };

  DraggableGridList.prototype._generateItemsFromDOM = function() {
    /**
     * Generate the structure of items used by the GridList lib, using the DOM
     * data of the children of the targeted element. The items will have an
     * additional reference to the initial DOM element attached, in order to
     * trace back to it and re-render it once its properties are changed by the
     * GridList lib
     */
    var _this = this,
        items = [],
        item;
    this.$items.each(function(i, element) {
      item = {
        $element: $(element)
      };
      $.extend(item, $(element).data());
      items.push(item);
    });
    return items;
  };

  DraggableGridList.prototype._getItemByElement = function(element) {
    // XXX: this could be optimized by storing the item reference inside the
    // meta data of the DOM element
    for (var i = 0; i < this.items.length; i++) {
      if (this.items[i].$element.is(element)) {
        return this.items[i];
      }
    }
  };

  DraggableGridList.prototype._calculateCellSize = function() {
    this._cellHeight = this.$element.height() / this.options.rows;
    this._cellWidth = this._cellHeight * this.options.widthHeightRatio;
    if (this.options.heightToFontSizeRatio) {
      this._fontSize = this._cellHeight * this.options.heightToFontSizeRatio;
    }
  };

  DraggableGridList.prototype._getItemWidth = function(item) {
    return item.w * this._cellWidth;
  };

  DraggableGridList.prototype._getItemHeight = function(item) {
    return item.h * this._cellHeight;
  };

  DraggableGridList.prototype._applySizeToItems = function() {
    for (var i = 0; i < this.items.length; i++) {
      this.items[i].$element.css({
        width: this._getItemWidth(this.items[i]),
        height: this._getItemHeight(this.items[i])
      });
    }
    if (this.options.heightToFontSizeRatio) {
      this.$items.css('font-size', this._fontSize);
    }
  };

  DraggableGridList.prototype._applyPositionToItems = function() {
    // TODO: Implement group separators
    for (var i = 0; i < this.items.length; i++) {
      // Don't interfere with the positions of the dragged items
      if (this.items[i].move) {
        continue;
      }
      this.items[i].$element.css({
        left: this.items[i].x * this._cellWidth,
        top: this.items[i].y * this._cellHeight
      });
    }
    // Update the width of the entire grid container with an extra column on
    // the right for extra dragging room
    this.$element.width((this.gridList.grid.length + 1) * this._cellWidth);
  };

  DraggableGridList.prototype._dragPositionChanged = function(newPosition) {
    if (!this._previousDragPosition) {
      return true;
    }
    return (newPosition[0] != this._previousDragPosition[0] ||
            newPosition[1] != this._previousDragPosition[1]);
  };

  DraggableGridList.prototype._snapItemPositionToGrid = function(item) {
    var position = item.$element.position(),
        row,
        col;
    position[0] -= this.$element.position().left;
    col = Math.round(position.left / this._cellWidth);
    row = Math.round(position.top / this._cellHeight);
    // Keep item position within the grid and don't let the item create more
    // than one extra column
    col = Math.max(col, 0);
    row = Math.max(row, 0);
    col = Math.min(col, this._maxGridCols);
    row = Math.min(row, this.options.rows - item.h);
    return [col, row];
  };

  DraggableGridList.prototype._highlightPositionForItem = function(item) {
    this.$positionHighlight.css({
      width: this._getItemWidth(item),
      height: this._getItemHeight(item),
      left: item.x * this._cellWidth,
      top: item.y * this._cellHeight
    }).show();
  };

  DraggableGridList.prototype._removePositionHighlight = function() {
    this.$positionHighlight.hide();
  };

  var _swapItems = function(from, to) {
    /**
     * We use the same two arrays to not constantly create new objects with
     * leaking potential. We assume the same number of items in both!
     */
    for (var i = 0; i < from.length; i++) {
      to[i] = $.extend({}, from[i]);
    }
  };

  $.fn.gridList = function(options) {
    if (!window.GridList) {
      throw new Error('GridList lib required');
    }
    var instance,
        method,
        args;
    if (typeof(options) == 'string') {
      method = options;
      args =  Array.prototype.slice.call(arguments, 1);
    }
    this.each(function() {
      instance = $(this).data('_gridList');
      if (!instance) {
        instance = new DraggableGridList(this, options);
        $(this).data('_gridList', instance);
      }
      if (method) {
        instance[method].apply(instance, args);
      }
    });
    // Maintain jQuery chain
    return this;
  };

})(jQuery, window, document);
<|endoftext|>


<commit_before>(function (tree) {

tree.mixin = {};
tree.mixin.Call = function (elements, args, index) {
    this.selector = new(tree.Selector)(elements);
    this.arguments = args;
    this.index = index;
};
tree.mixin.Call.prototype = {
    eval: function (env) {
        var mixins, rules = [], match = false;

        for (var i = 0; i < env.frames.length; i++) {
            if ((mixins = env.frames[i].find(this.selector)).length > 0) {
                for (var m = 0; m < mixins.length; m++) {
                    if (mixins[m].match(this.arguments, env)) {
                        try {
                            Array.prototype.push.apply(
                                  rules, mixins[m].eval(env, this.arguments).rules);
                            match = true;
                        } catch (e) {
                            throw { message: e.message, index: e.index, stack: e.stack, call: this.index };
                        }
                    }
                }
                if (match) {
                    return rules;
                } else {
                    throw { message: 'No matching definition was found for `' +
                                      this.selector.toCSS().trim() + '('      +
                                      this.arguments.map(function (a) {
                                          return a.toCSS();
                                      }).join(', ') + ")`",
                            index:   this.index };
                }
            }
        }
        throw { message: this.selector.toCSS().trim() + " is undefined",
                index: this.index };
    }
};

tree.mixin.Definition = function (name, params, rules) {
    this.name = name;
    this.selectors = [new(tree.Selector)([new(tree.Element)(null, name)])];
    this.params = params;
    this.arity = params.length;
    this.rules = rules;
    this._lookups = {};
    this.required = params.reduce(function (count, p) {
        if (!p.name || (p.name && !p.value)) { return count + 1 }
        else                                 { return count }
    }, 0);
    this.parent = tree.Ruleset.prototype;
    this.frames = [];
};
tree.mixin.Definition.prototype = {
    toCSS:     function ()     { return "" },
    variable:  function (name) { return this.parent.variable.call(this, name) },
    variables: function ()     { return this.parent.variables.call(this) },
    find:      function ()     { return this.parent.find.apply(this, arguments) },
    rulesets:  function ()     { return this.parent.rulesets.apply(this) },

    eval: function (env, args) {
        var frame = new(tree.Ruleset)(null, []), context;

        for (var i = 0, val; i < this.params.length; i++) {
            if (this.params[i].name) {
                if (val = (args && args[i]) || this.params[i].value) {
                    frame.rules.unshift(new(tree.Rule)(this.params[i].name, val.eval(env)));
                } else {
                    throw { message: "wrong number of arguments for " + this.name +
                            ' (' + args.length + ' for ' + this.arity + ')' };
                }
            }
        }
        return new(tree.Ruleset)(null, this.rules.slice(0)).eval({
            frames: [this, frame].concat(this.frames, env.frames)
        });
    },
    match: function (args, env) {
        var argsLength = (args && args.length) || 0, len;

        if (argsLength < this.required)                               { return false }
        if ((this.required > 0) && (argsLength > this.params.length)) { return false }

        len = Math.min(argsLength, this.arity);

        for (var i = 0; i < len; i++) {
            if (!this.params[i].name) {
                if (args[i].eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) {
                    return false;
                }
            }
        }
        return true;
    }
};

})(require('less/tree'));
<commit_msg>(new) JavaScript-style 'arguments' variable<commit_after>(function (tree) {

tree.mixin = {};
tree.mixin.Call = function (elements, args, index) {
    this.selector = new(tree.Selector)(elements);
    this.arguments = args;
    this.index = index;
};
tree.mixin.Call.prototype = {
    eval: function (env) {
        var mixins, rules = [], match = false;

        for (var i = 0; i < env.frames.length; i++) {
            if ((mixins = env.frames[i].find(this.selector)).length > 0) {
                for (var m = 0; m < mixins.length; m++) {
                    if (mixins[m].match(this.arguments, env)) {
                        try {
                            Array.prototype.push.apply(
                                  rules, mixins[m].eval(env, this.arguments).rules);
                            match = true;
                        } catch (e) {
                            throw { message: e.message, index: e.index, stack: e.stack, call: this.index };
                        }
                    }
                }
                if (match) {
                    return rules;
                } else {
                    throw { message: 'No matching definition was found for `' +
                                      this.selector.toCSS().trim() + '('      +
                                      this.arguments.map(function (a) {
                                          return a.toCSS();
                                      }).join(', ') + ")`",
                            index:   this.index };
                }
            }
        }
        throw { message: this.selector.toCSS().trim() + " is undefined",
                index: this.index };
    }
};

tree.mixin.Definition = function (name, params, rules) {
    this.name = name;
    this.selectors = [new(tree.Selector)([new(tree.Element)(null, name)])];
    this.params = params;
    this.arity = params.length;
    this.rules = rules;
    this._lookups = {};
    this.required = params.reduce(function (count, p) {
        if (!p.name || (p.name && !p.value)) { return count + 1 }
        else                                 { return count }
    }, 0);
    this.parent = tree.Ruleset.prototype;
    this.frames = [];
};
tree.mixin.Definition.prototype = {
    toCSS:     function ()     { return "" },
    variable:  function (name) { return this.parent.variable.call(this, name) },
    variables: function ()     { return this.parent.variables.call(this) },
    find:      function ()     { return this.parent.find.apply(this, arguments) },
    rulesets:  function ()     { return this.parent.rulesets.apply(this) },

    eval: function (env, args) {
        var frame = new(tree.Ruleset)(null, []), context;

        for (var i = 0, val; i < this.params.length; i++) {
            if (this.params[i].name) {
                if (val = (args && args[i]) || this.params[i].value) {
                    frame.rules.unshift(new(tree.Rule)(this.params[i].name, val.eval(env)));
                } else {
                    throw { message: "wrong number of arguments for " + this.name +
                            ' (' + args.length + ' for ' + this.arity + ')' };
                }
            }
        }
        frame.rules.unshift(new(tree.Rule)('@arguments', new(tree.Expression)(args)));

        return new(tree.Ruleset)(null, this.rules.slice(0)).eval({
            frames: [this, frame].concat(this.frames, env.frames)
        });
    },
    match: function (args, env) {
        var argsLength = (args && args.length) || 0, len;

        if (argsLength < this.required)                               { return false }
        if ((this.required > 0) && (argsLength > this.params.length)) { return false }

        len = Math.min(argsLength, this.arity);

        for (var i = 0; i < len; i++) {
            if (!this.params[i].name) {
                if (args[i].eval(env).toCSS() != this.params[i].value.eval(env).toCSS()) {
                    return false;
                }
            }
        }
        return true;
    }
};

})(require('less/tree'));
<|endoftext|>


<commit_before>'use strict';

function Virtual () {
  this.getters = [];
  this.setters = [];
}

/**
 * Defines a getter.
 *
 * @param {Function} fn
 * @return {VirtualType} this
 * @api public
 */

Virtual.prototype.get = function (fn) {
  this.getters.push(fn);
  return this;
};

/**
 * Defines a virtual setter.
 *
 * @param {Function} fn
 * @return {Virtual} this
 * @api public
 */

Virtual.prototype.set = function (fn) {
  this.setters.push(fn);
  return this;
};

//
// Expose the virtual method.
//
module.exports = Virtual;
<commit_msg>[fix] remove unused virtual<commit_after><|endoftext|>


<commit_before>/*
  node-http-proxy.js: http proxy for node.js

  Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, & Marak Squires

  Permission is hereby granted, free of charge, to any person obtaining
  a copy of this software and associated documentation files (the
  "Software"), to deal in the Software without restriction, including
  without limitation the rights to use, copy, modify, merge, publish,
  distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so, subject to
  the following conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

var sys = require('sys'),
    http = require('http'),
    events = require('events'),
    pool = require('pool'),
    min = 0,
    max = 100;

// Setup the PoolManager
var manager = pool.createPoolManager();
manager.setMinClients(min);
manager.setMaxClients(max);

exports.createServer = function () {
  var args, callback, port, host;
  args = Array.prototype.slice.call(arguments);
  callback = typeof args[args.length - 1] === 'function' && args.pop();
  if (args[0]) port = args[0];
  if (args[1]) host = args[1];

  var server = http.createServer(function (req, res){
    var proxy = new HttpProxy(req, res);

    proxy.emitter.on('proxy', function (err, body) {
      server.emit('proxy', err, body);
    });

    // If we were passed a callback to process the request
    // or response in some way, then call it.
    if(callback) {
      callback(req, res, proxy);
    }
    else {
      proxy.proxyRequest(port, server);
    }
  });

  return server;
};

exports.setMin = function (value) {
  min = value;
  manager.setMinClients(min);
};

exports.setMax = function (value) {
  max = value;
  manager.setMaxClients(max);
};

var HttpProxy = function (req, res) {
  this.emitter = new(events.EventEmitter);
  this.events = {};
  this.req = req;
  this.res = res;
  this.watch(req);
};

HttpProxy.prototype = {
  toArray: function (obj){
    var len = obj.length,
        arr = new Array(len);
    for (var i = 0; i < len; ++i) {
        arr[i] = obj[i];
    }
    return arr;
  },

  watch: function (req) {
    this.events = [];
    var self = this;

    this.onData = function () {
      self.events.push(['data'].concat(self.toArray(arguments)));
    };
    this.onEnd = function () {
      self.events.push(['end'].concat(self.toArray(arguments)));
    };

    req.addListener('data', this.onData);
    req.addListener('end', this.onEnd);
  },

  unwatch: function (req) {
    req.removeListener('data', this.onData);
    req.removeListener('end', this.onEnd);

    // Rebroadcast any events that have been buffered
    for (var i = 0, len = this.events.length; i < len; ++i) {
      req.emit.apply(req, this.events[i]);
    }
  },

  proxyRequest: function (port, server) {
    // Remark: nodeProxy.body exists solely for testability
    var self = this, req = this.req, res = this.res;
    self.body = '';

    // Open new HTTP request to internal resource with will act as a reverse proxy pass
    var p = manager.getPool(port, server);

    p.request(req.method, req.url, req.headers, function (reverse_proxy) {
      // Create an error handler so we can use it temporarily
      var error = function (err) {
        res.writeHead(200, {'Content-Type': 'text/plain'});

        if(req.method !== 'HEAD') {
          res.write('An error has occurred: ' + sys.puts(JSON.stringify(err)));
        }

        res.end();
      };

      // Add a listener for the connection timeout event
      reverse_proxy.addListener('error', error);

      // Add a listener for the reverse_proxy response event
      reverse_proxy.addListener('response', function (response) {
        if (response.headers.connection) {
          if (req.headers.connection) response.headers.connection = req.headers.connection;
          else response.headers.connection = 'close';
        }

        // Set the response headers of the client response
        res.writeHead(response.statusCode, response.headers);

        // Add event handler for the proxied response in chunks
        response.addListener('data', function (chunk) {
          if(req.method !== 'HEAD') {
            res.write(chunk, 'binary');
            self.body += chunk;
          }
        });

        // Add event listener for end of proxied response
        response.addListener('end', function () {
          // Remark: Emit the end event for testability
          self.emitter.emit('proxy', null, self.body);
          res.end();
        });
      });

      // Chunk the client request body as chunks from the proxied request come in
      req.addListener('data', function (chunk) {
        reverse_proxy.write(chunk, 'binary');
      })

      // At the end of the client request, we are going to stop the proxied request
      req.addListener('end', function () {
        reverse_proxy.end();
        reverse_proxy.removeListener('error', error);
      });

      self.unwatch(req);
    });
  }
};

exports.HttpProxy = HttpProxy;<commit_msg>[minor] Listen to error event on pool so we dont fail out unexpectedly anymore<commit_after>/*
  node-http-proxy.js: http proxy for node.js

  Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, & Marak Squires

  Permission is hereby granted, free of charge, to any person obtaining
  a copy of this software and associated documentation files (the
  "Software"), to deal in the Software without restriction, including
  without limitation the rights to use, copy, modify, merge, publish,
  distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so, subject to
  the following conditions:

  The above copyright notice and this permission notice shall be
  included in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

var sys = require('sys'),
    http = require('http'),
    events = require('events'),
    pool = require('pool'),
    min = 0,
    max = 100;

// Setup the PoolManager
var manager = pool.createPoolManager();
manager.setMinClients(min);
manager.setMaxClients(max);

exports.createServer = function () {
  var args, callback, port, host;
  args = Array.prototype.slice.call(arguments);
  callback = typeof args[args.length - 1] === 'function' && args.pop();
  if (args[0]) port = args[0];
  if (args[1]) host = args[1];

  var server = http.createServer(function (req, res){
    var proxy = new HttpProxy(req, res);

    proxy.emitter.on('proxy', function (err, body) {
      server.emit('proxy', err, body);
    });

    // If we were passed a callback to process the request
    // or response in some way, then call it.
    if(callback) {
      callback(req, res, proxy);
    }
    else {
      proxy.proxyRequest(port, server);
    }
  });

  return server;
};

exports.setMin = function (value) {
  min = value;
  manager.setMinClients(min);
};

exports.setMax = function (value) {
  max = value;
  manager.setMaxClients(max);
};

var HttpProxy = function (req, res) {
  this.emitter = new(events.EventEmitter);
  this.events = {};
  this.req = req;
  this.res = res;
  this.watch(req);
};

HttpProxy.prototype = {
  toArray: function (obj){
    var len = obj.length,
        arr = new Array(len);
    for (var i = 0; i < len; ++i) {
        arr[i] = obj[i];
    }
    return arr;
  },

  watch: function (req) {
    this.events = [];
    var self = this;

    this.onData = function () {
      self.events.push(['data'].concat(self.toArray(arguments)));
    };
    this.onEnd = function () {
      self.events.push(['end'].concat(self.toArray(arguments)));
    };

    req.addListener('data', this.onData);
    req.addListener('end', this.onEnd);
  },

  unwatch: function (req) {
    req.removeListener('data', this.onData);
    req.removeListener('end', this.onEnd);

    // Rebroadcast any events that have been buffered
    for (var i = 0, len = this.events.length; i < len; ++i) {
      req.emit.apply(req, this.events[i]);
    }
  },

  proxyRequest: function (port, server) {
    // Remark: nodeProxy.body exists solely for testability
    var self = this, req = this.req, res = this.res;
    self.body = '';

    // Open new HTTP request to internal resource with will act as a reverse proxy pass
    var p = manager.getPool(port, server);

    p.on('error', function (err) {
      // Remark: We should probably do something here
      // but this is a hot-fix because I don't think 'pool'
      // should be emitting this event.
    });

    p.request(req.method, req.url, req.headers, function (reverse_proxy) {
      // Create an error handler so we can use it temporarily
      var error = function (err) {
        res.writeHead(200, {'Content-Type': 'text/plain'});

        if(req.method !== 'HEAD') {
          res.write('An error has occurred: ' + sys.puts(JSON.stringify(err)));
        }

        res.end();
      };

      // Add a listener for the connection timeout event
      reverse_proxy.addListener('error', error);

      // Add a listener for the reverse_proxy response event
      reverse_proxy.addListener('response', function (response) {
        if (response.headers.connection) {
          if (req.headers.connection) response.headers.connection = req.headers.connection;
          else response.headers.connection = 'close';
        }

        // Set the response headers of the client response
        res.writeHead(response.statusCode, response.headers);

        // Add event handler for the proxied response in chunks
        response.addListener('data', function (chunk) {
          if(req.method !== 'HEAD') {
            res.write(chunk, 'binary');
            self.body += chunk;
          }
        });

        // Add event listener for end of proxied response
        response.addListener('end', function () {
          // Remark: Emit the end event for testability
          self.emitter.emit('proxy', null, self.body);
          res.end();
        });
      });

      // Chunk the client request body as chunks from the proxied request come in
      req.addListener('data', function (chunk) {
        reverse_proxy.write(chunk, 'binary');
      })

      // At the end of the client request, we are going to stop the proxied request
      req.addListener('end', function () {
        reverse_proxy.end();
        reverse_proxy.removeListener('error', error);
      });

      self.unwatch(req);
    });
  }
};

exports.HttpProxy = HttpProxy;<|endoftext|>


<commit_before>'use strict';

var id = require('../util/id');

var debug = require('../util/debug')('trade-area');

var TYPE = 'trade-area';

function TradeArea(inputNode, kind, time) {
    this.inputNode = inputNode;
    this.kind = kind; // walk, drive, bike
    this.time = time; // in seconds
}

module.exports.TYPE = TYPE;
module.exports.create = function(definition, factory, databaseService, callback) {
    factory.create(definition.params.source, function(err, node) {
        if (err) {
            return callback(err);
        }

        var kind = definition.params.kind || 'walk';
        if (!Number.isFinite(definition.params.time)) {
            return callback('Unexpected "time" param');
        }
        var time = definition.params.time;

        var tradeArea = new TradeArea(node, kind, time);

        createCacheTable(databaseService, node.getQuery(), tradeArea.getTargetTable(), kind, time, function(err) {
            if (err) {
                return callback(err);
            }

            return callback(null, tradeArea);
        });
    });
};

// ------------------------------ PUBLIC API ------------------------------ //

TradeArea.prototype.id = function() {
    return id(this.toJSON());
};

TradeArea.prototype.getQuery = function() {
    return 'select * from ' + this.getTargetTable();
};

TradeArea.prototype.getInputNodes = function() {
    return [this.inputNode];
};

TradeArea.prototype.toJSON = function() {
    return {
        type: TYPE,
        inputNodeId: this.inputNode.id(),
        kind: this.kind,
        time: this.time
    };
};

TradeArea.prototype.toDot = function() {
    return {
        type: TYPE,
        color: 'red',
        nodes: {
            inputNode: this.inputNode
        },
        attrs: {
            kind: this.kind,
            time: this.time
        }
    };
};

// ---------------------------- END PUBLIC API ---------------------------- //

TradeArea.prototype.getTargetTable = function() {
    return 'analysis_trade_area_' + this.id();
};

var SKIP_COLUMNS = {
    'the_geom': true,
    'the_geom_webmercator': true
};

function columnNames(columns) {
    return columns
        .map(function(column) { return column.name; })
        .filter(function(columnName) { return !SKIP_COLUMNS[columnName]; });
}

var kmhMap = {
    walk: 5,
    bike: 15,
    drive: 90
};

function fakeTradeAreaDistanceFrom(kind, time) {
    kind = kmhMap.hasOwnProperty(kind) ? kind : 'walk';
    var kmh = kmhMap[kind];
    return (time / 3600) * (kmh * 1e3);
}

function analysisQuery(inputQuery, columns, kind, time) {
    var distance = fakeTradeAreaDistanceFrom(kind, time);
    debug('For kind=%s, time=%d => distance=%d', kind, time, distance);
    return [
        'SELECT ST_Buffer(the_geom::geography, ' + distance + ')::geometry the_geom,',
        columnNames(columns).join(','),
        'FROM (' + inputQuery + ') _cdb_create_cache_table'
    ].join('\n');
}

function createCacheTableQuery(targetTableName, outputQuery) {
    return [
        'CREATE TABLE ' + targetTableName + ' AS',
        outputQuery,
        'LIMIT 0'
    ].join('\n');
}

function populateCacheTableQuery(targetTableName, outputQuery) {
    return [
        'INSERT INTO ' + targetTableName,
        outputQuery
    ].join('\n');
}

function createCacheTable(databaseService, inputQuery, targetTableName, kind, time, callback) {
    databaseService.run('select * from ' + targetTableName + ' limit 0', function(err) {
        if (!err) {
            return callback(null);
        }

        // if table does not exist, we create it.
        databaseService.getSchema(inputQuery, function(err, columns) {
            if (err) {
                return callback(err);
            }

            var outputQuery = analysisQuery(inputQuery, columns, kind, time);

            var createQuery = createCacheTableQuery(targetTableName, outputQuery);
            databaseService.run(createQuery, function(err) {
                if (err) {
                    return callback(err);
                }
                var populateQuery = populateCacheTableQuery(targetTableName, outputQuery);
                databaseService.enqueue(populateQuery, function(err, result) {
                    return callback(err, result);
                });
            });
        });
    });
}<commit_msg>new end line<commit_after>'use strict';

var id = require('../util/id');

var debug = require('../util/debug')('trade-area');

var TYPE = 'trade-area';

function TradeArea(inputNode, kind, time) {
    this.inputNode = inputNode;
    this.kind = kind; // walk, drive, bike
    this.time = time; // in seconds
}

module.exports.TYPE = TYPE;
module.exports.create = function(definition, factory, databaseService, callback) {
    factory.create(definition.params.source, function(err, node) {
        if (err) {
            return callback(err);
        }

        var kind = definition.params.kind || 'walk';
        if (!Number.isFinite(definition.params.time)) {
            return callback('Unexpected "time" param');
        }
        var time = definition.params.time;

        var tradeArea = new TradeArea(node, kind, time);

        createCacheTable(databaseService, node.getQuery(), tradeArea.getTargetTable(), kind, time, function(err) {
            if (err) {
                return callback(err);
            }

            return callback(null, tradeArea);
        });
    });
};

// ------------------------------ PUBLIC API ------------------------------ //

TradeArea.prototype.id = function() {
    return id(this.toJSON());
};

TradeArea.prototype.getQuery = function() {
    return 'select * from ' + this.getTargetTable();
};

TradeArea.prototype.getInputNodes = function() {
    return [this.inputNode];
};

TradeArea.prototype.toJSON = function() {
    return {
        type: TYPE,
        inputNodeId: this.inputNode.id(),
        kind: this.kind,
        time: this.time
    };
};

TradeArea.prototype.toDot = function() {
    return {
        type: TYPE,
        color: 'red',
        nodes: {
            inputNode: this.inputNode
        },
        attrs: {
            kind: this.kind,
            time: this.time
        }
    };
};

// ---------------------------- END PUBLIC API ---------------------------- //

TradeArea.prototype.getTargetTable = function() {
    return 'analysis_trade_area_' + this.id();
};

var SKIP_COLUMNS = {
    'the_geom': true,
    'the_geom_webmercator': true
};

function columnNames(columns) {
    return columns
        .map(function(column) { return column.name; })
        .filter(function(columnName) { return !SKIP_COLUMNS[columnName]; });
}

var kmhMap = {
    walk: 5,
    bike: 15,
    drive: 90
};

function fakeTradeAreaDistanceFrom(kind, time) {
    kind = kmhMap.hasOwnProperty(kind) ? kind : 'walk';
    var kmh = kmhMap[kind];
    return (time / 3600) * (kmh * 1e3);
}

function analysisQuery(inputQuery, columns, kind, time) {
    var distance = fakeTradeAreaDistanceFrom(kind, time);
    debug('For kind=%s, time=%d => distance=%d', kind, time, distance);
    return [
        'SELECT ST_Buffer(the_geom::geography, ' + distance + ')::geometry the_geom,',
        columnNames(columns).join(','),
        'FROM (' + inputQuery + ') _cdb_create_cache_table'
    ].join('\n');
}

function createCacheTableQuery(targetTableName, outputQuery) {
    return [
        'CREATE TABLE ' + targetTableName + ' AS',
        outputQuery,
        'LIMIT 0'
    ].join('\n');
}

function populateCacheTableQuery(targetTableName, outputQuery) {
    return [
        'INSERT INTO ' + targetTableName,
        outputQuery
    ].join('\n');
}

function createCacheTable(databaseService, inputQuery, targetTableName, kind, time, callback) {
    databaseService.run('select * from ' + targetTableName + ' limit 0', function(err) {
        if (!err) {
            return callback(null);
        }

        // if table does not exist, we create it.
        databaseService.getSchema(inputQuery, function(err, columns) {
            if (err) {
                return callback(err);
            }

            var outputQuery = analysisQuery(inputQuery, columns, kind, time);

            var createQuery = createCacheTableQuery(targetTableName, outputQuery);
            databaseService.run(createQuery, function(err) {
                if (err) {
                    return callback(err);
                }
                var populateQuery = populateCacheTableQuery(targetTableName, outputQuery);
                databaseService.enqueue(populateQuery, function(err, result) {
                    return callback(err, result);
                });
            });
        });
    });
}
<|endoftext|>