<commit_before>/**
 * @module myModule
 * @summary: This module's purpose is to:
 *
 * @description:
 *
 * Author: Justin Mooser
 * Created On: 2015-06-01.
 * @license Apache-2.0
 */

"use strict";

var util = require('util');

/**
 *
 * @param config
 * @param storage .save(): promise rejects if it should not be retried.
 * @param longTermStorage - .save(): resolves url to saved resource.  Rejects if it should not be retried.
 * @returns {{}}
 */
module.exports = function construct(config, storage, longTermStorage) {
  var m = {};
  config = config ? config : {};
  config = _.defaults(config, {
    saveEventsToLongTermStorage: true
  });

  m.processEvent = function(eventPayload, eventType) {
    var eventRow;
    return p.resolve().then(function() {
      return extractEventRow(eventPayload,eventType);
    })
    .then(function(eventrow) {
      eventRow = eventrow;
      return saveToLongTermStorage(eventRow, eventPayload);
    })
    .then(function(longTermStorageUrl) {
      console.log('Saved to', longTermStorageUrl);
        // the database only allows 100 characters.
      if (eventRow.details.length && eventRow.details.length > 100) {
        eventRow.details = eventRow.details.substr(0, 99);
      }
      if(eventRow.label.length > 24) {
        eventRow.label = eventRow.label.substr(0,24);
      }
      eventRow.url = longTermStorageUrl;
      eventRow.timestamp = Math.floor(new Date().getTime()/1000);
      return storage.save('eventlog', eventRow)
        .catch(function(err) {
          console.error('Error saving tracked event to storage.');
          console.error(err);
        });
    })
    .catch(function(err) {
      console.error('Unexpected error processing tracked event.');
      console.error(err);
    });
  };

  function extractEventRow(eventPayload, eventType) {
    eventType = eventType || 'bunyan-v1';
    // you have to extract the payload and the essential details about the logged event.
    if (eventType=='bunyan-v1') {
      var details = extractDetailsObject(eventPayload);

      var eventRow = {
        details: details,
        local_ts: Math.floor(Date.parse(eventPayload.time)/1000),
        component: eventPayload.name,
        host: eventPayload.hostname,
        label: eventPayload.eventLabel,
        env: eventPayload.env,
        pid: eventPayload.pid,
        app: eventPayload.app || 'Unknown App',
        version: eventType,
        level: eventPayload.level
      };

      return eventRow;
    }
  }

  function fixJSON(str) {
    var json = str.replace(/(['"])?([a-zA-Z0-9_]+)(['"])?:/g, '"$2": ');
    json = json.replace(/\'/g, '"');
    return json;
  }

  function extractDetailsObject(eventPayload) {
    var msg = eventPayload.msg;

    var detailsObjectStart = msg.indexOf('{');
    var parse = false;
    if (detailsObjectStart>=0) {
      parse = true;
    }
    else {
      detailsObjectStart = msg.indexOf('[');
      if (detailsObjectStart >= 0) parse = true;
    }

    if (parse) {
      var json = fixJSON(msg.substr(detailsObjectStart));
      return JSON.parse(json);
    }

    return { details: msg.substr(eventPayload.eventLabel.length+1) };
  }

  /**
   * Container names are per application per environment.
   * @note: In S3 a container name would reference a bucket.
   */
  function chooseStorageContainerName(eventRow) {
    if (!eventRow.app) throw 'chooseStorageContainerName eventRow.app is empty';
    if (!eventRow.env) warn('chooseStorageContainerName eventRow.env is empty');
    return eventRow.app + '-' + eventRow.env;
  }

  /**
   * Constructs an S3 friendly URL (uses random hash at start of key name to optimize S3 performance)
   * @param eventRow
   */
  function chooseStorageKey(eventRow) {
    return util.format("%s--%s-%s-%s", new Date().getUTCMilliseconds(), new Date().toISOString(), eventRow.component, eventRow.version);
  }

  function saveToLongTermStorage(eventRow, eventPayload) {
    var container = chooseStorageContainerName(eventRow);
    var key = chooseStorageKey(eventRow);

    return longTermStorage.save(container, key, eventPayload);
  }

  return m;
};
<commit_msg>Bugfix<commit_after>/**
 * @module myModule
 * @summary: This module's purpose is to:
 *
 * @description:
 *
 * Author: Justin Mooser
 * Created On: 2015-06-01.
 * @license Apache-2.0
 */

"use strict";

var util = require('util');

/**
 *
 * @param config
 * @param storage .save(): promise rejects if it should not be retried.
 * @param longTermStorage - .save(): resolves url to saved resource.  Rejects if it should not be retried.
 * @returns {{}}
 */
module.exports = function construct(config, storage, longTermStorage) {
  var m = {};
  config = config ? config : {};
  config = _.defaults(config, {
    saveEventsToLongTermStorage: true
  });

  m.processEvent = function(eventPayload, eventType) {
    var eventRow;
    return p.resolve().then(function() {
      return extractEventRow(eventPayload,eventType);
    })
    .then(function(eventrow) {
      eventRow = eventrow;
      return saveToLongTermStorage(eventRow, eventPayload);
    })
    .then(function(longTermStorageUrl) {
      console.log('Saved to', longTermStorageUrl);
        // the database only allows 100 characters.
      if (eventRow.details.length && eventRow.details.length > 100) {
        eventRow.details = eventRow.details.substr(0, 99);
      }
      if(eventRow.label.length > 24) {
        eventRow.label = eventRow.label.substr(0,24);
      }
      eventRow.url = longTermStorageUrl;
      eventRow.timestamp = Math.floor(new Date().getTime()/1000);
      return storage.save('eventlog', eventRow)
        .catch(function(err) {
          console.error('Error saving tracked event to storage.');
          console.error(err);
        });
    })
    .catch(function(err) {
      console.error('Unexpected error processing tracked event.');
      console.error(err);
    });
  };

  function extractEventRow(eventPayload, eventType) {
    eventType = eventType || 'bunyan-v1';
    // you have to extract the payload and the essential details about the logged event.
    if (eventType=='bunyan-v1') {
      var details = extractDetailsObject(eventPayload);

      var eventRow = {
        details: details,
        local_ts: Math.floor(Date.parse(eventPayload.time)/1000),
        component: eventPayload.name,
        host: eventPayload.hostname,
        label: eventPayload.eventLabel,
        env: eventPayload.env,
        pid: eventPayload.pid,
        app: eventPayload.app || 'UnknownApp',
        version: eventType,
        level: eventPayload.level
      };

      return eventRow;
    }
  }

  function fixJSON(str) {
    var json = str.replace(/(['"])?([a-zA-Z0-9_]+)(['"])?:/g, '"$2": ');
    json = json.replace(/\'/g, '"');
    return json;
  }

  function extractDetailsObject(eventPayload) {
    var msg = eventPayload.msg;

    var detailsObjectStart = msg.indexOf('{');
    var parse = false;
    if (detailsObjectStart>=0) {
      parse = true;
    }
    else {
      detailsObjectStart = msg.indexOf('[');
      if (detailsObjectStart >= 0) parse = true;
    }

    if (parse) {
      var json = fixJSON(msg.substr(detailsObjectStart));
      return JSON.parse(json);
    }

    return { details: msg.substr(eventPayload.eventLabel.length+1) };
  }

  /**
   * Container names are per application per environment.
   * @note: In S3 a container name would reference a bucket.
   */
  function chooseStorageContainerName(eventRow) {
    if (!eventRow.app) throw 'chooseStorageContainerName eventRow.app is empty';
    if (!eventRow.env) warn('chooseStorageContainerName eventRow.env is empty');
    return eventRow.app + '-' + eventRow.env;
  }

  /**
   * Constructs an S3 friendly URL (uses random hash at start of key name to optimize S3 performance)
   * @param eventRow
   */
  function chooseStorageKey(eventRow) {
    return util.format("%s--%s-%s-%s", new Date().getUTCMilliseconds(), new Date().toISOString(), eventRow.component, eventRow.version);
  }

  function saveToLongTermStorage(eventRow, eventPayload) {
    var container = chooseStorageContainerName(eventRow);
    var key = chooseStorageKey(eventRow);

    return longTermStorage.save(container, key, eventPayload);
  }

  return m;
};
<|endoftext|>


<commit_before>    function log(content) {
      document.body.appendChild(document.createTextNode(content + '\n'));
      console.log.apply(console, arguments);
    }

    window.onerror = function(err) {
      log("Error", err);
    };

    function Registr(){

      navigator.serviceWorker.register('sw.js', {
        scope: './'
      }).then(function(sw) {
        log("注册成功");
      }).catch(function(err) {
        log("Error" + err);
      });
    }

    function UnRegistr(){

          navigator.serviceWorker.getRegistration().then(function(registration) {
            if (registration) {
              registration.unregister('./');
              log("反注册成功");
            }

          })

    }

    if (navigator.serviceWorker && navigator.serviceWorker.getRegistrations) {

          navigator.serviceWorker.getRegistration().then(function(registration) {
            if (registration && registration.active) {
               log("sw当前已经注册")
            } else {
              log("sw当前没有注册")
            }

          })

    } else {

      setTimeout(function(){log("该浏览器不支持service worker")}, 500)
    }

    var KTestTime = 32;

    function sendRequest(url, endFunction, index, count) {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
          if (xhr.readyState == XMLHttpRequest.DONE) {

             if (++index < count) {

                sendRequest(url, endFunction, index, count)

             } else {

                endFunction(xhr)
             }

          }
      }
      xhr.open('GET', url + "&" + new Date().getTime(), true);
      xhr.send(null);

    }

    function startOriginalAjaxTest(){

      var start = new Date().getTime();

      var i = 0;

      sendRequest("jsonBig.json?", function(xhr){

          var end = new Date().getTime();
          log("耗时 " +  (end - start));

      }, i , KTestTime);

    }

    function startSWAjaxTest(){

      var start = new Date().getTime();

      var i = 0;

      sendRequest("jsonBig.json?", function(xhr){

          var end = new Date().getTime();
          log("SW注册后耗时 " + (end - start));

      }, i , KTestTime);

    }

    function startSWAjaxCacheTest() {

            var start = new Date().getTime();

      var i = 0;

      sendRequest("jsonBig.json?fake", function(xhr){

          var end = new Date().getTime();
          log("SW注册后缓存耗时 " + (end - start));

      }, i , KTestTime);

    }<commit_msg>no message<commit_after>    function log(content) {
      document.body.appendChild(document.createTextNode(content + '\n'));
      console.log.apply(console, arguments);
    }

    window.onerror = function(err) {
      log("Error", err);
    };

    function Registr(){

      navigator.serviceWorker.register('sw.js', {
        scope: './'
      }).then(function(sw) {
        log("注册成功");
      }).catch(function(err) {
        log("Error" + err);
      });
    }

    function UnRegistr(){

          navigator.serviceWorker.getRegistration().then(function(registration) {
            if (registration) {
              registration.unregister('./');
              log("反注册成功");
            }

          })

    }

    if (navigator.serviceWorker && navigator.serviceWorker.getRegistration) {

          navigator.serviceWorker.getRegistration().then(function(registration) {
            if (registration && registration.active) {
               log("sw当前已经注册")
            } else {
              log("sw当前没有注册")
            }

          })

    } else {

      setTimeout(function(){log("该浏览器不支持service worker")}, 500)
    }

    var KTestTime = 32;

    function sendRequest(url, endFunction, index, count) {
      var xhr = new XMLHttpRequest();
      xhr.onreadystatechange = function() {
          if (xhr.readyState == XMLHttpRequest.DONE) {

             if (++index < count) {

                sendRequest(url, endFunction, index, count)

             } else {

                endFunction(xhr)
             }

          }
      }
      xhr.open('GET', url + "&" + new Date().getTime(), true);
      xhr.send(null);

    }

    function startOriginalAjaxTest(){

      var start = new Date().getTime();

      var i = 0;

      sendRequest("jsonBig.json?", function(xhr){

          var end = new Date().getTime();
          log("耗时 " +  (end - start));

      }, i , KTestTime);

    }

    function startSWAjaxTest(){

      var start = new Date().getTime();

      var i = 0;

      sendRequest("jsonBig.json?", function(xhr){

          var end = new Date().getTime();
          log("SW注册后耗时 " + (end - start));

      }, i , KTestTime);

    }

    function startSWAjaxCacheTest() {

            var start = new Date().getTime();

      var i = 0;

      sendRequest("jsonBig.json?fake", function(xhr){

          var end = new Date().getTime();
          log("SW注册后缓存耗时 " + (end - start));

      }, i , KTestTime);

    }<|endoftext|>


<commit_before>#! /usr/bin/env node
"use strict";
const fs = require("fs");
const exec = require("child_process").exec;
const fileName = "src/js/mainSiteURLs.json";

function fileFinder(dir, pattern) {
  return new Promise((resolve, reject) => {
    var command = `find ${dir} ${pattern}`;
    exec(command, (err, stdout) => {
      return (err) ? reject(err) : resolve(stdout);
    });
  });
}

// Find css and JS files
const findJs = fileFinder(`src/js`, `-iname "*.js"`);
const findCss = fileFinder(`src/css`, `-iname "*.css" -or -iname "*.svg" -or -iname "*.png"`);

function processResults(results) {
  const paths = results
    .reduce((current, next) => next.concat(current), [])
    .split("\n")
    .filter(item => item)
    .map(item => item.replace(/^src\//, "/"))
    .sort();
  return JSON.stringify(paths.concat(["/"]), null, 2);
}

function writeFile(template) {
  return new Promise((resolve, reject) => {
    fs.writeFile(fileName, template, (err) => {
      return (err) ? reject(err) : resolve();
    });
  });
}

// Process and generate file
Promise.all([findJs, findCss])
  .then(processResults)
  .then(writeFile)
  .catch(err => console.error(err));
<commit_msg>Adjust URLs relative to new SW location<commit_after>#! /usr/bin/env node
"use strict";
const fs = require("fs");
const exec = require("child_process").exec;
const fileName = "src/js/mainSiteURLs.json";

function fileFinder(dir, pattern) {
  return new Promise((resolve, reject) => {
    var command = `find ${dir} ${pattern}`;
    exec(command, (err, stdout) => {
      return (err) ? reject(err) : resolve(stdout);
    });
  });
}

// Find css and JS files
const findJs = fileFinder(`src/js`, `-iname "*.js"`);
const findCss = fileFinder(`src/css`, `-iname "*.css" -or -iname "*.svg" -or -iname "*.png"`);

function processResults(results) {
  const paths = results
    .reduce((current, next) => next.concat(current), [])
    .split("\n")
    .filter(item => item)
    .map(item => item.replace(/^src\//, "./"))
    .sort();
  return JSON.stringify(paths, null, 2);
}

function writeFile(template) {
  return new Promise((resolve, reject) => {
    fs.writeFile(fileName, template, (err) => {
      return (err) ? reject(err) : resolve();
    });
  });
}

// Process and generate file
Promise.all([findJs, findCss])
  .then(processResults)
  .then(writeFile)
  .catch(err => console.error(err));
<|endoftext|>


<commit_before>'use strict';

const os = require('os');
const path = require('path');
const http = require('http');
const https = require('https');
const cluster = require('cluster');
const parser = require('body-parser');
const fs = require('graceful-fs');
const express = require('express');

const enums = require('./enums');
const config = require('./config');
const log = require('./logger')('Core');

const CPU_COUNT = os.cpus().length;
const PORT_HTTP = config.isDevelopment ? 3000 : 80;
const PORT_HTTPS = 443;
const KEEP_ALIVE = 300000; // 5 minutes
const JSON_LIMIT = '1mb'; // default '100kb'
const STATIC_ROOT = path.join(__dirname, '../static');

// fake domain logic component
const domain = require('./domain');

if (config.Server.cluster && cluster.isMaster) {
  log.info(`Cluster mode enabled, spawning ${CPU_COUNT} workers.`);
  for (let i = 0; i < CPU_COUNT; i++) {
    cluster.fork();
  }
  cluster.on('message', onWorkerMessage);
  cluster.on('online', onWorkerOnline);
  cluster.on('exit', onWorkerDeath);
} else {
  var app = express();
  // use qs as querystring parser
  app.use(parser.urlencoded({extended: true}));
  // parse JSON payloads
  app.use(parser.json({limit: JSON_LIMIT}));
  app.set('view engine', 'pug');

  if (config.isDevelopment) {
    // pretty JSON response (disable JSON minification)
    app.set('json spaces', 2);
    // pretty HTTP response (disable pug/HTML minification)
    app.locals.pretty = true;
  }

  const httpServer = http.createServer(app);
  httpServer.on('connection', socket => socket.setTimeout(KEEP_ALIVE));
  httpServer.listen(PORT_HTTP);

  if (config.Server.force_https) {
    log.debug('Enforcing secure connections.');
    const httpsServer = https.createServer({
      cert: fs.readFileSync(config.Server.certificatePath),
      key: fs.readFileSync(config.Server.certificateKeyPath)
    }, app);
    httpsServer.on('connection', socket => socket.setTimeout(KEEP_ALIVE));
    httpsServer.listen(PORT_HTTPS);
    app.use(forceHttps);
  }

  app.get('/', logRequest, (req, res) => {
    res.render('index', {title: 'mechanik', content: 'mechanik content'});
  });

  app.get('/async', logRequest, (req, res) => {
    domain.asyncFunction().then(results => {
      log.verbose('Domain method done.', results);
      res.json(results);
    }).catch(err => {
      log.error('Error executing domain method.', err);
      returnServerError(res, err);
    });
  });

  app.use('/favicon.ico', express.static(path.join(STATIC_ROOT, 'images/favicon.ico')));
  app.use('/apple-touch-icon.png', express.static(path.join(STATIC_ROOT, 'images/apple-touch-icon.png')));
  app.use('/favicon-32x32.png', express.static(path.join(STATIC_ROOT, 'images/favicon-32x32.png')));
  app.use('/favicon-16x16.png', express.static(path.join(STATIC_ROOT, 'images/favicon-16x16.png')));
  app.use('/static', express.static(STATIC_ROOT));

  // if none of the above, 404
  app.use(logRequest, (req, res) => returnNotFound(res, null, req));
}

function onWorkerMessage(worker, message, handle) {
  // log.verbose('Received IPC message from worker.', { pid: worker.process.pid, message, handle });
  if (message.type === enums.Message.Type.Log) {
    log.raw(message.payload.component, message.payload.level, message.payload.message, message.payload.object);
  } else {
    log.warn('Received strange message from worker, revolution might be underway.', {pid: worker.process.pid, worker, message, handle});
  }
}

function onWorkerOnline(worker) {
  log.info(`Worker ${worker.process.pid} went online.`);
}

function onWorkerDeath(worker, code, signal) {
  log.warn(`Worker ${worker.process.pid} died with ${signal}. Respawning...`);
  cluster.fork();
}

function forceHttps(req, res, next) {
  if (req.secure) {
    next();
  } else {
    res.writeHead(301, {Location: `https://${req.headers.host}${req.url}`});
    res.end();
  }
}

function returnBadRequest(res, message) {
  res.status(400).send(message ? message : 'Bad request.');
  log.warn('Returned 400 Bad Request.', message);
}

function returnUnauthorized(res, message) {
  res.status(401).send(message ? message : 'Unauthorized.');
  log.warn('Returned 401 Unauthorized.', message);
}

function returnNotFound(res, message, req) {
  message = message ? message : 'Not found.';
  res.status(404).send(message);
  log.warn('Returned 404 Not Found.', {message, path: req ? req.path : undefined});
}

function returnTooLarge(res, message) {
  res.status(413).send(message ? message : 'Payload too large.');
  log.warn('Returned 413 Payload Too Large.', message);
}

function returnNotImplemented(res, message) {
  res.status(501).send(message ? message : 'Not implemented.');
  log.warn('Returned 501 Not Implemented.', message);
}

function returnServerError(res, message) {
  res.status(500).send(message ? message : 'Server error.');
  log.warn('Returned 500 Server Error.', message);
}

function returnServerConflict(res, message) {
  res.status(409).send(message ? message : 'Server conflict.');
  log.warn('Returned 409 Server Conflict.', message);
}

function logRequest(req, res, next) {
  log.telemetry('Request received.', {
    path: req.path,
    method: req.method === 'GET' ? undefined : req.method,
    query: Object.keys(req.query).length > 0 ? req.query : undefined,
    body: req.body,
    ip: req.ip
  });
  next();
}

// https://nodejs.org/api/process.html
process.on('uncaughtException', e => {
  log.error('Uncaught exception.', e);
});

// https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly
process.on('unhandledRejection', (reason, promise) => {
  log.error('Unhandled rejection.', {reason, promise});
});

process.on('exit', () => {
  log.fatal('Process is exiting, goodbye.');
});

process.on('SIGINT', () => {
  log.warn('Got SIGINT. Exiting...');
  process.exit();
});
<commit_msg>Core changed ordering on event handlers and worker forks.<commit_after>'use strict';

const os = require('os');
const path = require('path');
const http = require('http');
const https = require('https');
const cluster = require('cluster');
const parser = require('body-parser');
const fs = require('graceful-fs');
const express = require('express');

const enums = require('./enums');
const config = require('./config');
const log = require('./logger')('Core');

const CPU_COUNT = os.cpus().length;
const PORT_HTTP = config.isDevelopment ? 3000 : 80;
const PORT_HTTPS = 443;
const KEEP_ALIVE = 300000; // 5 minutes
const JSON_LIMIT = '1mb'; // default '100kb'
const STATIC_ROOT = path.join(__dirname, '../static');

// fake domain logic component
const domain = require('./domain');

if (config.Server.cluster && cluster.isMaster) {
  log.info(`Cluster mode enabled, spawning ${CPU_COUNT} workers.`);
  // attach event handlers
  cluster.on('message', onWorkerMessage);
  cluster.on('online', onWorkerOnline);
  cluster.on('exit', onWorkerDeath);
  // spawn workers
  for (let i = 0; i < CPU_COUNT; i++) {
    cluster.fork();
  }

} else {
  var app = express();
  // use qs as querystring parser
  app.use(parser.urlencoded({extended: true}));
  // parse JSON payloads
  app.use(parser.json({limit: JSON_LIMIT}));
  app.set('view engine', 'pug');

  if (config.isDevelopment) {
    // pretty JSON response (disable JSON minification)
    app.set('json spaces', 2);
    // pretty HTTP response (disable pug/HTML minification)
    app.locals.pretty = true;
  }

  const httpServer = http.createServer(app);
  httpServer.on('connection', socket => socket.setTimeout(KEEP_ALIVE));
  httpServer.listen(PORT_HTTP);

  if (config.Server.force_https) {
    log.debug('Enforcing secure connections.');
    const httpsServer = https.createServer({
      cert: fs.readFileSync(config.Server.certificatePath),
      key: fs.readFileSync(config.Server.certificateKeyPath)
    }, app);
    httpsServer.on('connection', socket => socket.setTimeout(KEEP_ALIVE));
    httpsServer.listen(PORT_HTTPS);
    app.use(forceHttps);
  }

  app.get('/', logRequest, (req, res) => {
    res.render('index', {title: 'mechanik', content: 'mechanik content'});
  });

  app.get('/async', logRequest, (req, res) => {
    domain.asyncFunction().then(results => {
      log.verbose('Domain method done.', results);
      res.json(results);
    }).catch(err => {
      log.error('Error executing domain method.', err);
      returnServerError(res, err);
    });
  });

  app.use('/favicon.ico', express.static(path.join(STATIC_ROOT, 'images/favicon.ico')));
  app.use('/apple-touch-icon.png', express.static(path.join(STATIC_ROOT, 'images/apple-touch-icon.png')));
  app.use('/favicon-32x32.png', express.static(path.join(STATIC_ROOT, 'images/favicon-32x32.png')));
  app.use('/favicon-16x16.png', express.static(path.join(STATIC_ROOT, 'images/favicon-16x16.png')));
  app.use('/static', express.static(STATIC_ROOT));

  // if none of the above, 404
  app.use(logRequest, (req, res) => returnNotFound(res, null, req));
}

function onWorkerMessage(worker, message, handle) {
  // log.verbose('Received IPC message from worker.', { pid: worker.process.pid, message, handle });
  if (message.type === enums.Message.Type.Log) {
    log.raw(message.payload.component, message.payload.level, message.payload.message, message.payload.object);
  } else {
    log.warn('Received strange message from worker, revolution might be underway.', {pid: worker.process.pid, worker, message, handle});
  }
}

function onWorkerOnline(worker) {
  log.info(`Worker ${worker.process.pid} went online.`);
}

function onWorkerDeath(worker, code, signal) {
  log.warn(`Worker ${worker.process.pid} died with ${signal}. Respawning...`);
  cluster.fork();
}

function forceHttps(req, res, next) {
  if (req.secure) {
    next();
  } else {
    res.writeHead(301, {Location: `https://${req.headers.host}${req.url}`});
    res.end();
  }
}

function returnBadRequest(res, message) {
  res.status(400).send(message ? message : 'Bad request.');
  log.warn('Returned 400 Bad Request.', message);
}

function returnUnauthorized(res, message) {
  res.status(401).send(message ? message : 'Unauthorized.');
  log.warn('Returned 401 Unauthorized.', message);
}

function returnNotFound(res, message, req) {
  message = message ? message : 'Not found.';
  res.status(404).send(message);
  log.warn('Returned 404 Not Found.', {message, path: req ? req.path : undefined});
}

function returnTooLarge(res, message) {
  res.status(413).send(message ? message : 'Payload too large.');
  log.warn('Returned 413 Payload Too Large.', message);
}

function returnNotImplemented(res, message) {
  res.status(501).send(message ? message : 'Not implemented.');
  log.warn('Returned 501 Not Implemented.', message);
}

function returnServerError(res, message) {
  res.status(500).send(message ? message : 'Server error.');
  log.warn('Returned 500 Server Error.', message);
}

function returnServerConflict(res, message) {
  res.status(409).send(message ? message : 'Server conflict.');
  log.warn('Returned 409 Server Conflict.', message);
}

function logRequest(req, res, next) {
  log.telemetry('Request received.', {
    path: req.path,
    method: req.method === 'GET' ? undefined : req.method,
    query: Object.keys(req.query).length > 0 ? req.query : undefined,
    body: req.body,
    ip: req.ip
  });
  next();
}

// https://nodejs.org/api/process.html
process.on('uncaughtException', e => {
  log.error('Uncaught exception.', e);
});

// https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly
process.on('unhandledRejection', (reason, promise) => {
  log.error('Unhandled rejection.', {reason, promise});
});

process.on('exit', () => {
  log.fatal('Process is exiting, goodbye.');
});

process.on('SIGINT', () => {
  log.warn('Got SIGINT. Exiting...');
  process.exit();
});
<|endoftext|>


<commit_before>/*
MIT License

Copyright (c) 2016 Grall Arnaud

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.
*/
'use strict';

const EventEmitter = require('events');
const Unicast = require('unicast-definition');
const uuid = require('uuid/v4');
const LRU = require('lru-cache');
const VVwE = require('version-vector-with-exceptions');

class FBroadcastMessage {
	constructor (options) {
		this.value = options.value || null;
		this.id = options.id || null;
		this.ec = options.ec || { _e: 0, _c: 0};
		this.isReady = options.isReady || null;
	}
}

class FBroadcast extends EventEmitter {
	constructor (options) {
		super();
		if(options.foglet && options.protocol && options.size) {
			this.uid = uuid();
			this.protocol = 'fbroadcast-'+options.protocol;
			this.alsoMe = options.me || false;
			this.foglet = options.foglet;
			this.size = options.size || 100;

			this.sniffer = options.sniffer || function (message) {
				return message;
			};

			const lruOptions = {
				max: this.size
			};

			this.cache = new LRU(lruOptions);
			this.causality = new VVwE(uuid(), lruOptions);

			this.unicast = new Unicast(this.foglet.options.spray, this.protocol + '-unicast');

			const self = this;
			this.unicast.on('receive', (id, message) => {
				if(!self._stopPropagation(message)) {
					message = self.sniffer(message);

					self.cache.set(message, message);

					self._reviewCache(); // Emit all messages ready to emit and delete them from the cache

					self._resend(message);
				}
			});
		}else{
			return new Error('Not enough parameters', 'fbroadcast.js');
		}
	}

	send (message, isReady = null, delay = 0) {
		// console.log('delay:'+delay);
		if(this.alsoMe) {
			this.emit('receive', message);
		}
		const id = uuid();
		const messageToSend = new FBroadcastMessage({
			value : message,
			id,
			ec: this.causality.increment(),
			isReady
		});
		this.causality.incrementFrom(messageToSend.ec);

		const self = this;
		setTimeout(function () {
			self._resend(messageToSend);
		}, delay);

		// Causal Id of the message
		return messageToSend.ec;
	}

	_resend (message) {
		this.foglet._flog('resend:');
		// console.log(message);
		const neighbours = this.foglet.getNeighbours();
		const self = this;
		neighbours.forEach((peer) => {
			self.unicast.send(message, peer);
		});
	}

	_reviewCache () {
		let ready = false;
		const self = this;
		this.cache.rforEach( (value, key) => {
			// console.log(value);
			if(self.causality.isLower(value.ec)) {
				self.foglet._flog('we delete');
				self.cache.del(key);
			}else{
				// console.log('ec:' + JSON.stringify(value.ec));
				// console.log('isready:' + JSON.stringify(value.isReady));
				if(self.causality.isReady(value.isReady)) {
					self.foglet._flog('found && emit');
					ready = true;
					self.causality.incrementFrom(value.ec);
					self.emit('receive', value.value);
					self.cache.del(key);
				}
			}
		});
		// If we have found one element we
		if(ready) {
			this._reviewCache();
		}
	}

	_stopPropagation (message) {
		return message.ec && ( this.causality.isLower(message.ec) || this.cache.get(message.ec) !== undefined);
	}

}

module.exports = { FBroadcast };
<commit_msg>remove comments in fbroadcast<commit_after>/*
MIT License

Copyright (c) 2016 Grall Arnaud

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.
*/
'use strict';

const EventEmitter = require('events');
const Unicast = require('unicast-definition');
const uuid = require('uuid/v4');
const LRU = require('lru-cache');
const VVwE = require('version-vector-with-exceptions');

class FBroadcastMessage {
	constructor (options) {
		this.value = options.value || null;
		this.id = options.id || null;
		this.ec = options.ec || { _e: 0, _c: 0};
		this.isReady = options.isReady || null;
	}
}

class FBroadcast extends EventEmitter {
	constructor (options) {
		super();
		if(options.foglet && options.protocol && options.size) {
			this.uid = uuid();
			this.protocol = 'fbroadcast-'+options.protocol;
			this.alsoMe = options.me || false;
			this.foglet = options.foglet;
			this.size = options.size || 100;

			this.sniffer = options.sniffer || function (message) {
				return message;
			};

			const lruOptions = {
				max: this.size
			};

			this.cache = new LRU(lruOptions);
			this.causality = new VVwE(uuid(), lruOptions);

			this.unicast = new Unicast(this.foglet.options.spray, this.protocol + '-unicast');

			const self = this;
			this.unicast.on('receive', (id, message) => {
				if(!self._stopPropagation(message)) {
					message = self.sniffer(message);

					self.cache.set(message, message);

					self._reviewCache(); // Emit all messages ready to emit and delete them from the cache

					self._resend(message);
				}
			});
		}else{
			return new Error('Not enough parameters', 'fbroadcast.js');
		}
	}

	send (message, isReady = null, delay = 0) {
		// console.log('delay:'+delay);
		if(this.alsoMe) {
			this.emit('receive', message);
		}
		const id = uuid();
		const messageToSend = new FBroadcastMessage({
			value : message,
			id,
			ec: this.causality.increment(),
			isReady
		});
		this.causality.incrementFrom(messageToSend.ec);

		const self = this;
		setTimeout(function () {
			self._resend(messageToSend);
		}, delay);

		// Causal Id of the message
		return messageToSend.ec;
	}

	_resend (message) {
		this.foglet._flog('resend:');
		// console.log(message);
		const neighbours = this.foglet.getNeighbours();
		const self = this;
		neighbours.forEach((peer) => {
			self.unicast.send(message, peer);
		});
	}

	_reviewCache () {
		let ready = false;
		const self = this;
		this.cache.rforEach( (value, key) => {
			// console.log(value);
			if(self.causality.isLower(value.ec)) {
				self.cache.del(key);
			}else{
				// console.log('ec:' + JSON.stringify(value.ec));
				// console.log('isready:' + JSON.stringify(value.isReady));
				if(self.causality.isReady(value.isReady)) {
					ready = true;
					self.causality.incrementFrom(value.ec);
					self.emit('receive', value.value);
					self.cache.del(key);
				}
			}
		});
		// If we have found one element we
		if(ready) {
			this._reviewCache();
		}
	}

	_stopPropagation (message) {
		return message.ec && ( this.causality.isLower(message.ec) || this.cache.get(message.ec) !== undefined);
	}

}

module.exports = { FBroadcast };
<|endoftext|>


<commit_before>/**
 * PowerTip Core
 *
 * @fileoverview  Core variables, plugin object, and API.
 * @link          http://stevenbenner.github.com/jquery-powertip/
 * @author        Steven Benner (http://stevenbenner.com/)
 * @requires      jQuery 1.7+
 */

// useful private variables
var $document = $(document),
	$window = $(window),
	$body = $('body');

// constants
var DATA_DISPLAYCONTROLLER = 'displayController',
	DATA_HASACTIVEHOVER = 'hasActiveHover',
	DATA_HASMOUSEENTER = 'hasMouseEnter',
	DATA_FORCEDOPEN = 'forcedOpen',
	DATA_HASMOUSEMOVE = 'hasMouseMove',
	DATA_MOUSEONTOTIP = 'mouseOnToPopup',
	DATA_ORIGINALTITLE = 'originalTitle',
	DATA_POWERTIP = 'powertip',
	DATA_POWERTIPJQ = 'powertipjq',
	DATA_POWERTIPTARGET = 'powertiptarget',
	EVENT_NAMESPACE = '.powertip',
	RAD2DEG = 180 / Math.PI;

/**
 * Session data
 * Private properties global to all powerTip instances
 */
var session = {
	elements: null,
	tooltips: null,
	isTipOpen: false,
	isFixedTipOpen: false,
	isClosing: false,
	tipOpenImminent: false,
	activeHover: null,
	currentX: 0,
	currentY: 0,
	previousX: 0,
	previousY: 0,
	desyncTimeout: null,
	closeDelayTimeout: null,
	mouseTrackingActive: false,
	delayInProgress: false,
	windowWidth: 0,
	windowHeight: 0,
	scrollTop: 0,
	scrollLeft: 0
};

/**
 * Collision enumeration
 * @enum {number}
 */
var Collision = {
	none: 0,
	top: 1,
	bottom: 2,
	left: 4,
	right: 8
};

/**
 * Display hover tooltips on the matched elements.
 * @param {(Object|string)} opts The options object to use for the plugin, or
 *     the name of a method to invoke on the first matched element.
 * @param {*=} [arg] Argument for an invoked method (optional).
 * @return {jQuery} jQuery object for the matched selectors.
 */
$.fn.powerTip = function(opts, arg) {
	var targetElements = this,
		options,
		tipController;

	// don't do any work if there were no matched elements
	if (!targetElements.length) {
		return targetElements;
	}

	// handle api method calls on the plugin, e.g. powerTip('hide')
	if ($.type(opts) === 'string' && $.powerTip[opts]) {
		return $.powerTip[opts].call(targetElements, targetElements, arg);
	}

	// extend options and instantiate TooltipController
	options = $.extend({}, $.fn.powerTip.defaults, opts);
	tipController = new TooltipController(options);

	// hook mouse and viewport dimension tracking
	initTracking();

	// setup the elements
	targetElements.each(function elementSetup() {
		var $this = $(this),
			dataPowertip = $this.data(DATA_POWERTIP),
			dataElem = $this.data(DATA_POWERTIPJQ),
			dataTarget = $this.data(DATA_POWERTIPTARGET),
			title;

		// handle repeated powerTip calls on the same element by destroying the
		// original instance hooked to it and replacing it with this call
		if ($this.data(DATA_DISPLAYCONTROLLER)) {
			$.powerTip.destroy($this);
		}

		// attempt to use title attribute text if there is no data-powertip,
		// data-powertipjq or data-powertiptarget. If we do use the title
		// attribute, delete the attribute so the browser will not show it
		title = $this.attr('title');
		if (!dataPowertip && !dataTarget && !dataElem && title) {
			$this.data(DATA_POWERTIP, title);
			$this.data(DATA_ORIGINALTITLE, title);
			$this.removeAttr('title');
		}

		// create hover controllers for each element
		$this.data(
			DATA_DISPLAYCONTROLLER,
			new DisplayController($this, options, tipController)
		);
	});

	// attach events to matched elements if the manual option is not enabled
	if (!options.manual) {
		// attach open events
		$.each(options.openEvents, function(idx, evt) {
			if ($.inArray(evt, options.closeEvents) > -1) {
				// event is in both openEvents and closeEvents, so toggle it
				targetElements.on(evt + EVENT_NAMESPACE, function elementToggle(event) {
					$.powerTip.toggle(this, event);
				});
			} else {
				targetElements.on(evt + EVENT_NAMESPACE, function elementOpen(event) {
					$.powerTip.show(this, event);
				});
			}
		});

		// attach close events
		$.each(options.closeEvents, function(idx, evt) {
			if ($.inArray(evt, options.openEvents) < 0) {
				targetElements.on(evt + EVENT_NAMESPACE, function elementClose(event) {
					// set immediate to true for any event without mouse info
					$.powerTip.hide(this, typeof event.pageX !== 'number');
				});
			}
		});

		// attach escape key close event
		targetElements.on('keydown' + EVENT_NAMESPACE, function elementKeyDown(event) {
			// always close tooltip when the escape key is pressed
			if (event.keyCode === 27) {
				$.powerTip.hide(this, true);
			}
		});
	}

	// remember elements that the plugin is attached to
	session.elements = session.elements ? session.elements.add(targetElements) : targetElements;

	return targetElements;
};

/**
 * Default options for the powerTip plugin.
 */
$.fn.powerTip.defaults = {
	fadeInTime: 200,
	fadeOutTime: 100,
	followMouse: false,
	popupId: 'powerTip',
	intentSensitivity: 7,
	intentPollInterval: 100,
	closeDelay: 100,
	placement: 'n',
	smartPlacement: false,
	offset: 10,
	mouseOnToPopup: false,
	manual: false,
	openEvents: [ 'mouseenter', 'focus' ],
	closeEvents: [ 'mouseleave', 'blur' ]
};

/**
 * Default smart placement priority lists.
 * The first item in the array is the highest priority, the last is the lowest.
 * The last item is also the default, which will be used if all previous options
 * do not fit.
 */
$.fn.powerTip.smartPlacementLists = {
	n: ['n', 'ne', 'nw', 's'],
	e: ['e', 'ne', 'se', 'w', 'nw', 'sw', 'n', 's', 'e'],
	s: ['s', 'se', 'sw', 'n'],
	w: ['w', 'nw', 'sw', 'e', 'ne', 'se', 'n', 's', 'w'],
	nw: ['nw', 'w', 'sw', 'n', 's', 'se', 'nw'],
	ne: ['ne', 'e', 'se', 'n', 's', 'sw', 'ne'],
	sw: ['sw', 'w', 'nw', 's', 'n', 'ne', 'sw'],
	se: ['se', 'e', 'ne', 's', 'n', 'nw', 'se'],
	'nw-alt': ['nw-alt', 'n', 'ne-alt', 'sw-alt', 's', 'se-alt', 'w', 'e'],
	'ne-alt': ['ne-alt', 'n', 'nw-alt', 'se-alt', 's', 'sw-alt', 'e', 'w'],
	'sw-alt': ['sw-alt', 's', 'se-alt', 'nw-alt', 'n', 'ne-alt', 'w', 'e'],
	'se-alt': ['se-alt', 's', 'sw-alt', 'ne-alt', 'n', 'nw-alt', 'e', 'w']
};

/**
 * Public API
 */
$.powerTip = {
	/**
	 * Attempts to show the tooltip for the specified element.
	 * @param {jQuery|Element} element The element to open the tooltip for.
	 * @param {jQuery.Event=} event jQuery event for hover intent and mouse
	 *     tracking (optional).
	 * @return {jQuery|Element} The original jQuery object or DOM Element.
	 */
	show: function apiShowTip(element, event) {
		// if we were supplied an event with a pageX property then it is a mouse
		// event with the information necessary to do hover intent testing
		if (event && typeof event.pageX === 'number') {
			trackMouse(event);
			session.previousX = event.pageX;
			session.previousY = event.pageY;
			$(element).data(DATA_DISPLAYCONTROLLER).show();
		} else {
			$(element).first().data(DATA_DISPLAYCONTROLLER).show(true, true);
		}
		return element;
	},

	/**
	 * Repositions the tooltip on the element.
	 * @param {jQuery|Element} element The element the tooltip is shown for.
	 * @return {jQuery|Element} The original jQuery object or DOM Element.
	 */
	reposition: function apiResetPosition(element) {
		$(element).first().data(DATA_DISPLAYCONTROLLER).resetPosition();
		return element;
	},

	/**
	 * Attempts to close any open tooltips.
	 * @param {(jQuery|Element)=} element The element with the tooltip that
	 *     should be closed (optional).
	 * @param {boolean=} immediate Disable close delay (optional).
	 * @return {jQuery|Element|undefined} The original jQuery object or DOM
	 *     Element, if one was specified.
	 */
	hide: function apiCloseTip(element, immediate) {
		if (element) {
			$(element).first().data(DATA_DISPLAYCONTROLLER).hide(immediate);
		} else {
			if (session.activeHover) {
				session.activeHover.data(DATA_DISPLAYCONTROLLER).hide(true);
			}
		}
		return element;
	},

	/**
	 * Toggles the tooltip for the specified element. This will open a closed
	 * tooltip, or close and open tooltip.
	 * @param {(jQuery|Element)} element The element with the tooltip that
	 *     should be toggled.
	 * @param {jQuery.Event=} event jQuery event for hover intent and mouse
	 *     tracking (optional).
	 */
	toggle: function apiToggle(element, event) {
		if (session.activeHover && session.activeHover.is(element)) {
			// tooltip for element is active, so close it
			$.powerTip.hide(element, event);
		} else {
			// tooltip for element is not active, so open it
			$.powerTip.show(element, event);
		}
	},

	/**
	 * Destroy and roll back any powerTip() instance on the specified elements.
	 * If no elements are specified then all elements that the plugin is
	 * currently attached to will be rolled back.
	 * @param {(jQuery|Element)=} element The element with the powerTip instance.
	 * @return {jQuery|Element|undefined} The original jQuery object or DOM
	 *     Element, if one was specified.
	 */
	destroy: function apiDestroy(element) {
		var $element = element ? $(element) : session.elements;

		// if the plugin is not hooked to any elements then there is no point
		// trying to destroy anything, or dealing with the possible errors
		if (!session.elements || session.elements.length === 0) {
			return element;
		}

		// unhook events and destroy plugin changes to each element
		$element.off(EVENT_NAMESPACE).each(function destroy() {
			var $this = $(this),
				dataAttributes = [
					DATA_ORIGINALTITLE,
					DATA_DISPLAYCONTROLLER,
					DATA_HASACTIVEHOVER,
					DATA_FORCEDOPEN
				];

			// revert title attribute
			if ($this.data(DATA_ORIGINALTITLE)) {
				$this.attr('title', $this.data(DATA_ORIGINALTITLE));
				dataAttributes.push(DATA_POWERTIP);
			}

			// remove data attributes
			$this.removeData(dataAttributes);
		});

		// remove destroyed element from active elements collection
		session.elements = session.elements.not($element);

		// if there are no active elements left then we will unhook all of the
		// events that we've bound code to and remove the tooltip elements
		if (session.elements.length === 0) {
			$window.off(EVENT_NAMESPACE);
			$document.off(EVENT_NAMESPACE);
			session.mouseTrackingActive = false;
			session.tooltips.remove();
			session.tooltips = null;
		}

		return element;
	}
};

// API aliasing
$.powerTip.showTip = $.powerTip.show;
$.powerTip.closeTip = $.powerTip.hide;
<commit_msg>Corrected invalid argument passed to hide().<commit_after>/**
 * PowerTip Core
 *
 * @fileoverview  Core variables, plugin object, and API.
 * @link          http://stevenbenner.github.com/jquery-powertip/
 * @author        Steven Benner (http://stevenbenner.com/)
 * @requires      jQuery 1.7+
 */

// useful private variables
var $document = $(document),
	$window = $(window),
	$body = $('body');

// constants
var DATA_DISPLAYCONTROLLER = 'displayController',
	DATA_HASACTIVEHOVER = 'hasActiveHover',
	DATA_HASMOUSEENTER = 'hasMouseEnter',
	DATA_FORCEDOPEN = 'forcedOpen',
	DATA_HASMOUSEMOVE = 'hasMouseMove',
	DATA_MOUSEONTOTIP = 'mouseOnToPopup',
	DATA_ORIGINALTITLE = 'originalTitle',
	DATA_POWERTIP = 'powertip',
	DATA_POWERTIPJQ = 'powertipjq',
	DATA_POWERTIPTARGET = 'powertiptarget',
	EVENT_NAMESPACE = '.powertip',
	RAD2DEG = 180 / Math.PI;

/**
 * Session data
 * Private properties global to all powerTip instances
 */
var session = {
	elements: null,
	tooltips: null,
	isTipOpen: false,
	isFixedTipOpen: false,
	isClosing: false,
	tipOpenImminent: false,
	activeHover: null,
	currentX: 0,
	currentY: 0,
	previousX: 0,
	previousY: 0,
	desyncTimeout: null,
	closeDelayTimeout: null,
	mouseTrackingActive: false,
	delayInProgress: false,
	windowWidth: 0,
	windowHeight: 0,
	scrollTop: 0,
	scrollLeft: 0
};

/**
 * Collision enumeration
 * @enum {number}
 */
var Collision = {
	none: 0,
	top: 1,
	bottom: 2,
	left: 4,
	right: 8
};

/**
 * Display hover tooltips on the matched elements.
 * @param {(Object|string)} opts The options object to use for the plugin, or
 *     the name of a method to invoke on the first matched element.
 * @param {*=} [arg] Argument for an invoked method (optional).
 * @return {jQuery} jQuery object for the matched selectors.
 */
$.fn.powerTip = function(opts, arg) {
	var targetElements = this,
		options,
		tipController;

	// don't do any work if there were no matched elements
	if (!targetElements.length) {
		return targetElements;
	}

	// handle api method calls on the plugin, e.g. powerTip('hide')
	if ($.type(opts) === 'string' && $.powerTip[opts]) {
		return $.powerTip[opts].call(targetElements, targetElements, arg);
	}

	// extend options and instantiate TooltipController
	options = $.extend({}, $.fn.powerTip.defaults, opts);
	tipController = new TooltipController(options);

	// hook mouse and viewport dimension tracking
	initTracking();

	// setup the elements
	targetElements.each(function elementSetup() {
		var $this = $(this),
			dataPowertip = $this.data(DATA_POWERTIP),
			dataElem = $this.data(DATA_POWERTIPJQ),
			dataTarget = $this.data(DATA_POWERTIPTARGET),
			title;

		// handle repeated powerTip calls on the same element by destroying the
		// original instance hooked to it and replacing it with this call
		if ($this.data(DATA_DISPLAYCONTROLLER)) {
			$.powerTip.destroy($this);
		}

		// attempt to use title attribute text if there is no data-powertip,
		// data-powertipjq or data-powertiptarget. If we do use the title
		// attribute, delete the attribute so the browser will not show it
		title = $this.attr('title');
		if (!dataPowertip && !dataTarget && !dataElem && title) {
			$this.data(DATA_POWERTIP, title);
			$this.data(DATA_ORIGINALTITLE, title);
			$this.removeAttr('title');
		}

		// create hover controllers for each element
		$this.data(
			DATA_DISPLAYCONTROLLER,
			new DisplayController($this, options, tipController)
		);
	});

	// attach events to matched elements if the manual option is not enabled
	if (!options.manual) {
		// attach open events
		$.each(options.openEvents, function(idx, evt) {
			if ($.inArray(evt, options.closeEvents) > -1) {
				// event is in both openEvents and closeEvents, so toggle it
				targetElements.on(evt + EVENT_NAMESPACE, function elementToggle(event) {
					$.powerTip.toggle(this, event);
				});
			} else {
				targetElements.on(evt + EVENT_NAMESPACE, function elementOpen(event) {
					$.powerTip.show(this, event);
				});
			}
		});

		// attach close events
		$.each(options.closeEvents, function(idx, evt) {
			if ($.inArray(evt, options.openEvents) < 0) {
				targetElements.on(evt + EVENT_NAMESPACE, function elementClose(event) {
					// set immediate to true for any event without mouse info
					$.powerTip.hide(this, typeof event.pageX !== 'number');
				});
			}
		});

		// attach escape key close event
		targetElements.on('keydown' + EVENT_NAMESPACE, function elementKeyDown(event) {
			// always close tooltip when the escape key is pressed
			if (event.keyCode === 27) {
				$.powerTip.hide(this, true);
			}
		});
	}

	// remember elements that the plugin is attached to
	session.elements = session.elements ? session.elements.add(targetElements) : targetElements;

	return targetElements;
};

/**
 * Default options for the powerTip plugin.
 */
$.fn.powerTip.defaults = {
	fadeInTime: 200,
	fadeOutTime: 100,
	followMouse: false,
	popupId: 'powerTip',
	intentSensitivity: 7,
	intentPollInterval: 100,
	closeDelay: 100,
	placement: 'n',
	smartPlacement: false,
	offset: 10,
	mouseOnToPopup: false,
	manual: false,
	openEvents: [ 'mouseenter', 'focus' ],
	closeEvents: [ 'mouseleave', 'blur' ]
};

/**
 * Default smart placement priority lists.
 * The first item in the array is the highest priority, the last is the lowest.
 * The last item is also the default, which will be used if all previous options
 * do not fit.
 */
$.fn.powerTip.smartPlacementLists = {
	n: ['n', 'ne', 'nw', 's'],
	e: ['e', 'ne', 'se', 'w', 'nw', 'sw', 'n', 's', 'e'],
	s: ['s', 'se', 'sw', 'n'],
	w: ['w', 'nw', 'sw', 'e', 'ne', 'se', 'n', 's', 'w'],
	nw: ['nw', 'w', 'sw', 'n', 's', 'se', 'nw'],
	ne: ['ne', 'e', 'se', 'n', 's', 'sw', 'ne'],
	sw: ['sw', 'w', 'nw', 's', 'n', 'ne', 'sw'],
	se: ['se', 'e', 'ne', 's', 'n', 'nw', 'se'],
	'nw-alt': ['nw-alt', 'n', 'ne-alt', 'sw-alt', 's', 'se-alt', 'w', 'e'],
	'ne-alt': ['ne-alt', 'n', 'nw-alt', 'se-alt', 's', 'sw-alt', 'e', 'w'],
	'sw-alt': ['sw-alt', 's', 'se-alt', 'nw-alt', 'n', 'ne-alt', 'w', 'e'],
	'se-alt': ['se-alt', 's', 'sw-alt', 'ne-alt', 'n', 'nw-alt', 'e', 'w']
};

/**
 * Public API
 */
$.powerTip = {
	/**
	 * Attempts to show the tooltip for the specified element.
	 * @param {jQuery|Element} element The element to open the tooltip for.
	 * @param {jQuery.Event=} event jQuery event for hover intent and mouse
	 *     tracking (optional).
	 * @return {jQuery|Element} The original jQuery object or DOM Element.
	 */
	show: function apiShowTip(element, event) {
		// if we were supplied an event with a pageX property then it is a mouse
		// event with the information necessary to do hover intent testing
		if (event && typeof event.pageX === 'number') {
			trackMouse(event);
			session.previousX = event.pageX;
			session.previousY = event.pageY;
			$(element).data(DATA_DISPLAYCONTROLLER).show();
		} else {
			$(element).first().data(DATA_DISPLAYCONTROLLER).show(true, true);
		}
		return element;
	},

	/**
	 * Repositions the tooltip on the element.
	 * @param {jQuery|Element} element The element the tooltip is shown for.
	 * @return {jQuery|Element} The original jQuery object or DOM Element.
	 */
	reposition: function apiResetPosition(element) {
		$(element).first().data(DATA_DISPLAYCONTROLLER).resetPosition();
		return element;
	},

	/**
	 * Attempts to close any open tooltips.
	 * @param {(jQuery|Element)=} element The element with the tooltip that
	 *     should be closed (optional).
	 * @param {boolean=} immediate Disable close delay (optional).
	 * @return {jQuery|Element|undefined} The original jQuery object or DOM
	 *     Element, if one was specified.
	 */
	hide: function apiCloseTip(element, immediate) {
		if (element) {
			$(element).first().data(DATA_DISPLAYCONTROLLER).hide(immediate);
		} else {
			if (session.activeHover) {
				session.activeHover.data(DATA_DISPLAYCONTROLLER).hide(true);
			}
		}
		return element;
	},

	/**
	 * Toggles the tooltip for the specified element. This will open a closed
	 * tooltip, or close and open tooltip.
	 * @param {(jQuery|Element)} element The element with the tooltip that
	 *     should be toggled.
	 * @param {jQuery.Event=} event jQuery event for hover intent and mouse
	 *     tracking (optional).
	 */
	toggle: function apiToggle(element, event) {
		if (session.activeHover && session.activeHover.is(element)) {
			// tooltip for element is active, so close it
			$.powerTip.hide(element, typeof event.pageX !== 'number');
		} else {
			// tooltip for element is not active, so open it
			$.powerTip.show(element, event);
		}
	},

	/**
	 * Destroy and roll back any powerTip() instance on the specified elements.
	 * If no elements are specified then all elements that the plugin is
	 * currently attached to will be rolled back.
	 * @param {(jQuery|Element)=} element The element with the powerTip instance.
	 * @return {jQuery|Element|undefined} The original jQuery object or DOM
	 *     Element, if one was specified.
	 */
	destroy: function apiDestroy(element) {
		var $element = element ? $(element) : session.elements;

		// if the plugin is not hooked to any elements then there is no point
		// trying to destroy anything, or dealing with the possible errors
		if (!session.elements || session.elements.length === 0) {
			return element;
		}

		// unhook events and destroy plugin changes to each element
		$element.off(EVENT_NAMESPACE).each(function destroy() {
			var $this = $(this),
				dataAttributes = [
					DATA_ORIGINALTITLE,
					DATA_DISPLAYCONTROLLER,
					DATA_HASACTIVEHOVER,
					DATA_FORCEDOPEN
				];

			// revert title attribute
			if ($this.data(DATA_ORIGINALTITLE)) {
				$this.attr('title', $this.data(DATA_ORIGINALTITLE));
				dataAttributes.push(DATA_POWERTIP);
			}

			// remove data attributes
			$this.removeData(dataAttributes);
		});

		// remove destroyed element from active elements collection
		session.elements = session.elements.not($element);

		// if there are no active elements left then we will unhook all of the
		// events that we've bound code to and remove the tooltip elements
		if (session.elements.length === 0) {
			$window.off(EVENT_NAMESPACE);
			$document.off(EVENT_NAMESPACE);
			session.mouseTrackingActive = false;
			session.tooltips.remove();
			session.tooltips = null;
		}

		return element;
	}
};

// API aliasing
$.powerTip.showTip = $.powerTip.show;
$.powerTip.closeTip = $.powerTip.hide;
<|endoftext|>


<commit_before>(function( window, $ ) {
  var $window, $doc;

  $window = $(window);
  $doc = $( document.documentElement );

  window.componentNamespace = window.componentNamespace || window;

  Function.prototype.bind = function( context ) {
    var self = this;

    return function() {
      self.apply( context, arguments );
    };
  };

  var Tau = window.componentNamespace.Tau = function( element ) {
    var startIndex;

    this.element = element;
    this.$element = $( element );
    this.$initial = this.$element.find( "img" );
    this.$loading = this.$element.find( ".loading" );

    // make sure the initial image stays visible after enhance
    this.$initial.addClass( "focused" );

    // hide all other images
    this.$element.addClass( "enhanced" );

    // create the rest of the images
    this.createImages();

    // set the initial index and image
    this.goto( 0 );

    // start the automatic rotation
    this.autoRotate();

    // setup the event bindings for touch drag and mouse drag rotation
    this.bind();
  };

  Tau.autoRotateDelay = 64;
  Tau.verticalScrollRatio = 4;

  Tau.prototype.change = function( delta ) {
    this.goto( this.index + delta );
  };

  Tau.prototype.goto = function( index ) {
    var $next, normalizedIndex;

    index = index % this.$images.length;

    // stay within the bounds of the array
    normalizedIndex = (this.$images.length + index) % this.$images.length;

    // set the next image that's going to be shown/focused
    $next = this.$images.eq( normalizedIndex );

    // skip this action if the desired image isn't loaded yet
    // TODO do something fancier here instead of just throwing up hands
    if( !$next[0].tauImageLoaded ) {
      this.showLoading();
      return;
    }

    // record the updated index only after advancing is possible
    this.index = normalizedIndex;

    // hide the old focused image
    if( this.$current ) {
      this.$current.removeClass( "focused" );
    }

    // record the current focused image and make it visible
    this.$current = $next;

    // show the new focused image
    this.$current.addClass( "focused" );
  };

  // TODO transplant the attributes from the initial image
  Tau.prototype.createImages = function() {
    var src, frames, $new, boundImageLoaded;

    // avoid doing rebinding in a tight loop
    boundImageLoaded = this.imageLoaded.bind( this );

    src = this.$initial.attr( "data-src-template" );
    frames = parseInt( this.$initial.attr( "data-frames" ), 10 );

    // mark the initial image as loaded
    this.markImageLoaded( this.$initial[0] );

    for( var i = 2; i <= frames; i++) {
      $new = $( "" );

      // record when each image has loaded
      $new.bind( "load", boundImageLoaded );

      this.$element.append( $new );
    }

    this.$images = this.$element.find( "img" );
  };

  Tau.prototype.imageLoaded = function( event ) {
    this.markImageLoaded( event.target );
    this.hideLoading();
  };

  Tau.prototype.markImageLoaded = function( element ) {
    element.tauImageLoaded = true;
  };

  Tau.prototype.bind = function() {
    this.$element.bind( "mousedown touchstart", this.track.bind(this) );
  };

  Tau.prototype.autoRotate = function() {
    this.autoInterval = setInterval(function() {
      this.change( 1 );
    }.bind(this), Tau.autoRotateDelay);
  };

  Tau.prototype.stopAutoRotate = function() {
    clearInterval( this.autoInterval );
  };

  Tau.prototype.track = function( event ) {
    var point;

    // prevent dragging behavior for mousedown
    if( event.type === "mousedown"  ){
      event.preventDefault();
    }

    if( this.tracking ) {
      return;
    }

    $doc.one( "mouseup", this.release.bind(this) );
    $doc.one( "touchend", this.release.bind(this) );

    this.tracking = true;

    this.prevPoint = undefined;
    this.prevTime = undefined;

    this.cursorGrab();

    // calculate/store how many pixels makes for an image switch
    this.rotateThreshold = $doc[0].clientWidth / this.$images.length;

    // record the x for threshold calculations
    point = this.getPoint( event );
    this.downX = point.x;
    this.downY = point.y;
    this.downIndex = this.index;

    $doc.bind( "mousemove", this.rotate.bind(this) );
    $doc.bind( "touchmove", this.rotate.bind(this) );
  };

  Tau.prototype.release = function( event ) {
    var distance, time, point, velocity;

    if( !this.prevPoint || !this.prevPrevPoint ) {
      return;
    }

    distance = this.prevPoint.x - this.prevPrevPoint.x;
    time = this.prevTime - this.prevPrevTime;
    velocity = distance / (time/20);

    // console.log( "distance: " + distance );
    // console.log( "time: " + time );
    console.log( "initial velocity: " + velocity );

    var timeout = setInterval(function() {
      if( !this.prevPoint || !this.prevPrevPoint ) {
        clearInterval(timeout);
        return;
      }

      var point = {
        x: this.prevPoint.x + velocity,
        y: this.prevPoint.y
      };

      this.rotate( undefined, point );

      if( velocity > 0 ){
        velocity = velocity - 2;

        if( velocity <= 0 ){
          clearInterval(timeout);
        }
      } else {
        velocity = velocity + 2;

        if( velocity >= 0 ){
          clearInterval(timeout);
        }
      }
    }.bind(this), 20);

    this.cursorRelease();

    // TODO sort out why shoestring borks when unbinding with a string split list
    $doc.unbind( "mousemove", this.rotate.bind(this) );
    $doc.unbind( "touchmove", this.rotate.bind(this) );

    this.tracking = false;
  };

  Tau.prototype.cursorGrab = function() {
    $doc.addClass( "grabbing" );
  };

  Tau.prototype.cursorRelease = function() {
    $doc.removeClass( "grabbing" );
  };

  Tau.prototype.showLoading = function() {
    this.$loading.attr( "style" , "display: block" );
  };

  Tau.prototype.hideLoading = function() {
    this.$loading.attr( "style" , "display: none" );
  };

  Tau.prototype.getPoint = function( event ) {
    var touch = event.touches || (event.originalEvent && event.originalEvent.touches);

    if( touch ){
      return {
        x: touch[0].pageX,
        y: touch[0].pageY
      };
    }

    return {
      x: event.pageX,
      y: event.pageY
    };
  };

  Tau.prototype.rotate = function( event, point ) {
    var deltaX, deltaY, point;

    point = point || this.getPoint( event );

    deltaX = point.x - this.downX;
    deltaY = point.y - this.downY;

    // if the movement on the Y dominates X then skip and allow scroll
    if( Math.abs(deltaY) / Math.abs(deltaX) >= Tau.verticalScrollRatio ) {
      return;
    }

    // NOTE works better on mousedown, here allows autorotate to continue on scroll though
    this.stopAutoRotate();

    this.prevPrevTime = this.prevTime;
    this.prevPrevPoint = this.prevPoint;

    // record the most recent drag point for decel on release
    this.prevTime = Date.now();
    this.prevPoint = point;

    // NOTE to reverse the spin direction add the delta/thresh to the downIndex
    // NOTE it might be better to prevent anyway for slow drags across the image
    if( Math.abs(deltaX) >= this.rotateThreshold ) {
      event && event.preventDefault();
      this.goto( this.downIndex - Math.round(deltaX / this.rotateThreshold) );
    }
  };

  Math.easeOutExpo = function (t, b, c, d) {
    return c * ( -Math.pow( 2, -10 * t/d ) + 1 ) + b;
  };
})(this, jQuery);
<commit_msg>split event binding from rotate method<commit_after>(function( window, $ ) {
  var $window, $doc;

  $window = $(window);
  $doc = $( document.documentElement );

  window.componentNamespace = window.componentNamespace || window;

  Function.prototype.bind = function( context ) {
    var self = this;

    return function() {
      self.apply( context, arguments );
    };
  };

  var Tau = window.componentNamespace.Tau = function( element ) {
    var startIndex;

    this.element = element;
    this.$element = $( element );
    this.$initial = this.$element.find( "img" );
    this.$loading = this.$element.find( ".loading" );

    // make sure the initial image stays visible after enhance
    this.$initial.addClass( "focused" );

    // hide all other images
    this.$element.addClass( "enhanced" );

    // create the rest of the images
    this.createImages();

    // set the initial index and image
    this.goto( 0 );

    // start the automatic rotation
    this.autoRotate();

    // setup the event bindings for touch drag and mouse drag rotation
    this.bind();
  };

  Tau.autoRotateDelay = 64;
  Tau.verticalScrollRatio = 4;

  Tau.prototype.change = function( delta ) {
    this.goto( this.index + delta );
  };

  Tau.prototype.goto = function( index ) {
    var $next, normalizedIndex;

    index = index % this.$images.length;

    // stay within the bounds of the array
    normalizedIndex = (this.$images.length + index) % this.$images.length;

    // set the next image that's going to be shown/focused
    $next = this.$images.eq( normalizedIndex );

    // skip this action if the desired image isn't loaded yet
    // TODO do something fancier here instead of just throwing up hands
    if( !$next[0].tauImageLoaded ) {
      this.showLoading();
      return;
    }

    // record the updated index only after advancing is possible
    this.index = normalizedIndex;

    // hide the old focused image
    if( this.$current ) {
      this.$current.removeClass( "focused" );
    }

    // record the current focused image and make it visible
    this.$current = $next;

    // show the new focused image
    this.$current.addClass( "focused" );
  };

  // TODO transplant the attributes from the initial image
  Tau.prototype.createImages = function() {
    var src, frames, $new, boundImageLoaded;

    // avoid doing rebinding in a tight loop
    boundImageLoaded = this.imageLoaded.bind( this );

    src = this.$initial.attr( "data-src-template" );
    frames = parseInt( this.$initial.attr( "data-frames" ), 10 );

    // mark the initial image as loaded
    this.markImageLoaded( this.$initial[0] );

    for( var i = 2; i <= frames; i++) {
      $new = $( "" );

      // record when each image has loaded
      $new.bind( "load", boundImageLoaded );

      this.$element.append( $new );
    }

    this.$images = this.$element.find( "img" );
  };

  Tau.prototype.imageLoaded = function( event ) {
    this.markImageLoaded( event.target );
    this.hideLoading();
  };

  Tau.prototype.markImageLoaded = function( element ) {
    element.tauImageLoaded = true;
  };

  Tau.prototype.bind = function() {
    this.$element.bind( "mousedown touchstart", this.track.bind(this) );
  };

  Tau.prototype.autoRotate = function() {
    this.autoInterval = setInterval(function() {
      this.change( 1 );
    }.bind(this), Tau.autoRotateDelay);
  };

  Tau.prototype.stopAutoRotate = function() {
    clearInterval( this.autoInterval );
  };

  Tau.prototype.track = function( event ) {
    var point;

    // prevent dragging behavior for mousedown
    if( event.type === "mousedown"  ){
      event.preventDefault();
    }

    if( this.tracking ) {
      return;
    }

    $doc.one( "mouseup", this.release.bind(this) );
    $doc.one( "touchend", this.release.bind(this) );

    this.tracking = true;

    this.prevPoint = undefined;
    this.prevTime = undefined;

    this.cursorGrab();

    // calculate/store how many pixels makes for an image switch
    this.rotateThreshold = $doc[0].clientWidth / this.$images.length;

    // record the x for threshold calculations
    point = this.getPoint( event );
    this.downX = point.x;
    this.downY = point.y;
    this.downIndex = this.index;

    $doc.bind( "mousemove", this.rotateEvent.bind(this) );
    $doc.bind( "touchmove", this.rotateEvent.bind(this) );
  };

  Tau.prototype.release = function( event ) {
    var distance, time, point, velocity;

    if( !this.prevPoint || !this.prevPrevPoint ) {
      return;
    }

    distance = this.prevPoint.x - this.prevPrevPoint.x;
    time = this.prevTime - this.prevPrevTime;
    velocity = distance / (time/20);

    // console.log( "distance: " + distance );
    // console.log( "time: " + time );
    console.log( "initial velocity: " + velocity );

    var timeout = setInterval(function() {
      if( !this.prevPoint || !this.prevPrevPoint ) {
        clearInterval(timeout);
        return;
      }

      var point = {
        x: this.prevPoint.x + velocity,
        y: this.prevPoint.y
      };

      this.rotate( point );

      if( velocity > 0 ){
        velocity = velocity - 3;

        if( velocity <= 0 ){
          clearInterval(timeout);
        }
      } else {
        velocity = velocity + 2;

        if( velocity >= 0 ){
          clearInterval(timeout);
        }
      }
    }.bind(this), 20);

    this.cursorRelease();

    // TODO sort out why shoestring borks when unbinding with a string split list
    $doc.unbind( "mousemove", this.rotateEvent.bind(this) );
    $doc.unbind( "touchmove", this.rotateEvent.bind(this) );

    this.tracking = false;
  };

  Tau.prototype.cursorGrab = function() {
    $doc.addClass( "grabbing" );
  };

  Tau.prototype.cursorRelease = function() {
    $doc.removeClass( "grabbing" );
  };

  Tau.prototype.showLoading = function() {
    this.$loading.attr( "style" , "display: block" );
  };

  Tau.prototype.hideLoading = function() {
    this.$loading.attr( "style" , "display: none" );
  };

  Tau.prototype.getPoint = function( event ) {
    var touch = event.touches || (event.originalEvent && event.originalEvent.touches);

    if( touch ){
      return {
        x: touch[0].pageX,
        y: touch[0].pageY
      };
    }

    return {
      x: event.pageX,
      y: event.pageY
    };
  };

  Tau.prototype.rotateEvent = function( event ) {
    if( this.rotate(this.getPoint(event)) ){
      event.preventDefault();
    };
  };

  Tau.prototype.rotate = function( point ) {
    var deltaX, deltaY, point;

    point = point || this.getPoint( event );

    deltaX = point.x - this.downX;
    deltaY = point.y - this.downY;

    // if the movement on the Y dominates X then skip and allow scroll
    if( Math.abs(deltaY) / Math.abs(deltaX) >= Tau.verticalScrollRatio ) {
      return false;
    }

    // NOTE works better on mousedown, here allows autorotate to continue on scroll though
    this.stopAutoRotate();

    this.prevPrevTime = this.prevTime;
    this.prevPrevPoint = this.prevPoint;

    // record the most recent drag point for decel on release
    this.prevTime = Date.now();
    this.prevPoint = point;

    // NOTE to reverse the spin direction add the delta/thresh to the downIndex
    // NOTE it might be better to prevent anyway for slow drags across the image
    if( Math.abs(deltaX) >= this.rotateThreshold ) {
      this.goto( this.downIndex - Math.round(deltaX / this.rotateThreshold) );
      return true;
    }
  };

  Math.easeOutExpo = function (t, b, c, d) {
    return c * ( -Math.pow( 2, -10 * t/d ) + 1 ) + b;
  };
})(this, jQuery);
<|endoftext|>


<commit_before>var core = (function() {
    "use strict";

    function TableRowIterator( el, logger ) {
        if ( typeof el === 'undefined' ) throw new Error('You must provide a table element');
        this._el = el;
        this._cursor = 1;
        this._columns = ['year', 'make', 'model', 'branch', 'location', 'stockNumber',
                            'closingDate', 'reservePrice'];

        if ( $('tr', this._el).length === 0 && logger && logger.warn )
            logger.warn('The provided table does not have any rows');
    }

    TableRowIterator.prototype.hasNext = function hasNext() {
        return this._currentRow() ? this._currentRow().length > 0 : false;
    };

    TableRowIterator.prototype.next = function next() {
        if ( !this.hasNext() ) throw new Error('StopIteration');

        var data = this._serializeRowData(this._currentRow());
        this._cursor = this._cursor + 1;
        return data;
    };

    TableRowIterator.prototype._currentRow = function _currentRow() {
        return this._el.find('tr:eq(' + this._cursor + ')');
    };

    TableRowIterator.prototype._serializeRowData = function _serializeRowData( row ) {
        var serialized = {},
            cells = row.find('td'),
            i = 0,
            max = this._columns.length;

        for (; i < max; i++) {
            serialized[this._columns[i]] = cells[i].innerText;
        }
        return serialized;
    };

    TableRowIterator.prototype.reset = function reset() {
        this._cursor = 1;
    };

    function ItemRepository( localStorageWrapper, mapper ) {
        if ( typeof localStorageWrapper === 'undefined' )
            throw new Error('You must provide a localStorageWrapper');
        if ( typeof mapper !== 'undefined' && (!mapper.toModel && !mapper.toJS) )
             throw new Error('Mapper must implement "toModel" and "toJS"');

        this._ls = localStorageWrapper;
        this._Model = mapper;
        this._mapper = mapper;
    }

    ItemRepository.prototype.add = function add( item ) {
        if ( typeof this._mapper === 'undefined' ) return this._ls.save( item );
        return this._ls.save( this._toJS( item ) );
    };

    ItemRepository.prototype.update = function update( item ) {
        if ( typeof this._mapper === 'undefined' ) return this._ls.update( item );
        return this._ls.update( this._toJS( item ) );
    };

    ItemRepository.prototype.remove = function remove( query ) {
        return this._ls.destroy( query );
    };

    ItemRepository.prototype.find = function find( query ) {
        var entities = this._ls.find( query );

        if ( typeof this._mapper === 'undefined' ) return entities;
        return this._toModels( entities );
    };

    ItemRepository.prototype.get = function get( id ) {
        var entity = this._ls.get( id );

        if ( !entity ) return null;
        if ( typeof this._mapper === 'undefined' ) return entity;
        return this._toModel( entity );
    };

    ItemRepository.prototype.all = function all() {
        var entities = this._ls.all();

        if ( typeof this._Model === 'undefined' ) return entities;
        return this._toModels( entities );
    };

    ItemRepository.prototype.count = function count() {
        return this._ls.size();
    };

    ItemRepository.prototype._toModels = function( entities ) {
        var i = 0,
            max = entities.length,
            models = [];

        for (; i < max; i++) {
            models.push( this._toModel( entities[i] ) );
        }
        return models;
    };

    ItemRepository.prototype._toModel = function( entity ) {
        return new this._mapper.toModel( entity );
    };

    ItemRepository.prototype._toJS = function( model ) {
        return new this._mapper.toJS( model );
    };

    function ItemSummaryImporter( tableRowIterator, itemRepository ) {
        this._iter = tableRowIterator;
        this._repo = itemRepository;
    }

    ItemSummaryImporter.prototype.run = function( force ) {
        while( this._iter.hasNext() ) {
            var item = this._iter.next();
            if ( !this._repo.get( item.stockNumber ) || force ) {
                this._repo.add( item );
            }
        }
    };

    return {
        TableRowIterator: TableRowIterator,
        ItemRepository: ItemRepository,
        ItemSummaryImporter: ItemSummaryImporter
    };
})();
<commit_msg>Minor cleanup<commit_after>var core = (function() {
    "use strict";

    function TableRowIterator( el, logger ) {
        if ( typeof el === 'undefined' ) throw new Error('You must provide a table element');
        this._el = el;
        this._cursor = 1;
        this._columns = ['year', 'make', 'model', 'branch', 'location', 'stockNumber',
                            'closingDate', 'reservePrice'];

        if ( $('tr', this._el).length === 0 && logger && logger.warn )
            logger.warn('The provided table does not have any rows');
    }

    TableRowIterator.prototype.hasNext = function hasNext() {
        return this._currentRow() ? this._currentRow().length > 0 : false;
    };

    TableRowIterator.prototype.next = function next() {
        if ( !this.hasNext() ) throw new Error('StopIteration');

        var data = this._serializeRowData(this._currentRow());
        this._cursor = this._cursor + 1;
        return data;
    };

    TableRowIterator.prototype._currentRow = function _currentRow() {
        return this._el.find('tr:eq(' + this._cursor + ')');
    };

    TableRowIterator.prototype._serializeRowData = function _serializeRowData( row ) {
        var serialized = {},
            cells = row.find('td'),
            i = 0,
            max = this._columns.length;

        for (; i < max; i++) {
            serialized[this._columns[i]] = cells[i].innerText;
        }
        return serialized;
    };

    TableRowIterator.prototype.reset = function reset() {
        this._cursor = 1;
    };

    function ItemRepository( localStorageWrapper, mapper ) {
        if ( typeof localStorageWrapper === 'undefined' )
            throw new Error('You must provide a localStorageWrapper');
        if ( typeof mapper !== 'undefined' && (!mapper.toModel && !mapper.toJS) )
             throw new Error('Mapper must implement "toModel" and "toJS"');

        this._ls = localStorageWrapper;
        this._mapper = mapper;
    }

    ItemRepository.prototype.add = function add( item ) {
        if ( typeof this._mapper === 'undefined' ) return this._ls.save( item );
        return this._ls.save( this._toJS( item ) );
    };

    ItemRepository.prototype.update = function update( item ) {
        if ( typeof this._mapper === 'undefined' ) return this._ls.update( item );
        return this._ls.update( this._toJS( item ) );
    };

    ItemRepository.prototype.remove = function remove( query ) {
        return this._ls.destroy( query );
    };

    ItemRepository.prototype.find = function find( query ) {
        var entities = this._ls.find( query );

        if ( typeof this._mapper === 'undefined' ) return entities;
        return this._toModels( entities );
    };

    ItemRepository.prototype.get = function get( id ) {
        var entity = this._ls.get( id );

        if ( !entity ) return null;
        if ( typeof this._mapper === 'undefined' ) return entity;
        return this._toModel( entity );
    };

    ItemRepository.prototype.all = function all() {
        var entities = this._ls.all();

        if ( typeof this._mapper === 'undefined' ) return entities;
        return this._toModels( entities );
    };

    ItemRepository.prototype.count = function count() {
        return this._ls.size();
    };

    ItemRepository.prototype._toModels = function( entities ) {
        var i = 0,
            max = entities.length,
            models = [];

        for (; i < max; i++) {
            models.push( this._toModel( entities[i] ) );
        }
        return models;
    };

    ItemRepository.prototype._toModel = function( entity ) {
        return new this._mapper.toModel( entity );
    };

    ItemRepository.prototype._toJS = function( model ) {
        return new this._mapper.toJS( model );
    };

    function ItemSummaryImporter( tableRowIterator, itemRepository ) {
        this._iter = tableRowIterator;
        this._repo = itemRepository;
    }

    ItemSummaryImporter.prototype.run = function( force ) {
        while( this._iter.hasNext() ) {
            var item = this._iter.next();
            if ( !this._repo.get( item.stockNumber ) || force ) {
                this._repo.add( item );
            }
        }
    };

    return {
        TableRowIterator: TableRowIterator,
        ItemRepository: ItemRepository,
        ItemSummaryImporter: ItemSummaryImporter
    };
})();
<|endoftext|>