<commit_before>'use strict';

var asynEach = require('async-foreach').forEach,
    chalk = require('chalk'),
    fs = require('graceful-fs'),
    globby = require('globby'),
    path = require('path'),
    pkg = require('../package.json'),
    PDF = require('pdfkit'),
    Q = require('q'),
    sizeOf = require('image-size'),
    utils = require('./utils'),
    Website = require('./website');

/**
 * Public: Constructor for PDFShooter
 * @author Steven Britton
 * @date   2016-04-14
 * @param  {Object}  options [description]
 */
var PDFShooter = function(parameters) {

    utils.log.verbose('PDFShooter: constructor', parameters);

    this.options = parameters;
    this.userPDFOptions = parameters.pdf_options;
    this.website = new Website(this.options);

};

function getDate() {

    var d = new Date(),
        date =
        d.getUTCFullYear() + '-' +
        (d.getUTCMonth() + 1) + '-' +
        d.getUTCDate();
    return date;
}

/**
 * Gets all files based on viewport size
 * @author Steven Britton
 * @date   2016-04-26
 * @param  {String}   outputPath Path to generated screenshots
 * @param  {String}   size       Viewport size
 * @return {Array}               Array of paths to images
 */
function getFiles(outputPath, size) {
    return globby.sync([path.join(outputPath, '**', '*' + size + '.png')]);
}

/**
 * Write text to PDF object
 * @author Steven Britton
 * @date   2016-04-25
 * @param  {Object}   pdf        pdf-kit object
 * @param  {String}   title      title header for field
 * @param  {String}   field      field that's part of the cotent collection
 * @param  {String}   fieldColor font color for the field parameter
 */
function writeTextPDF(pdf, title, field, fieldColor) {

    fieldColor = fieldColor || 'black';

    pdf.fontSize(30)
        .fillColor('black')
        .text(title);

    pdf.fontSize(20)
        .fillColor(fieldColor)
        .text(field);

    pdf.moveDown();
}

/**
 * Generates a PDF containing the passed array of images along with metadata collected from Siteshooter
 * @author Steven Britton
 * @date   2016-05-10
 * @param  {Object}   pdfShooter
 * @param  {Array}    arrImages
 * @param  {[type]}   dimensions
 * @param  {String}   domainName
 * @param  {String}   outputPath
 * @param  {[type]}   viewport
 * @param  {Function} done
 * @return {Function} callback
 */
function createPDF(pdfShooter, arrImages, dimensions, domainName, outputPath, viewport, done) {

    utils.log.verbose('createPDF', arguments);

    var date = getDate(),
        credit,
        page,
        pdfPath = path.resolve(outputPath, utils.urlToDir(domainName) + '-' + viewport + '-' + date + '.pdf'),
        tempURL,
        writeStream;

    utils.log.verbose('pdfPath', pdfPath);

    credit = 'Siteshooter v' + pkg.version + ' - https://www.devopsgroup.io';

    var pdf = new PDF({
        layout: 'portrait',
        margins: {
            top: 72,
            left: 60,
            right: 72,
            bottom: 20
        }
    });

    writeStream = fs.createWriteStream(pdfPath);

    pdf.pipe(writeStream);

    pdf.pipe(fs.createWriteStream(pdfPath));

    arrImages.forEach(function(png, i) {

        page = png.replace('screenshots/', '');

        // remove from last slash and remaining chars (e.g., /1600.png)
        page = page.substr(0, page.lastIndexOf('/'));

        // get page information stored in website collection
        page = pdfShooter.website.contentCollection.pages.filter(function(item, indx, arr) {

            // remove protocol and last slash
            tempURL = item.loc
                .replace(/^(http|https):\/\//, '')
                .replace(/\/$/, '');

            return (tempURL === page);
        });

        // do we have page specific data?
        if (Array.isArray(page) && page.length > 0) {

            // add site-level information to cover page
            if (i === 0) {

                writeTextPDF(pdf, 'Date', date);

                writeTextPDF(pdf, 'Website', page[0].loc, 'blue');

                writeTextPDF(pdf, 'Viewport', viewport + ' (' + dimensions.width + 'x' + dimensions.height + ')');

                writeTextPDF(pdf, 'Google Analytics Version', page[0].meta.gaVersion);

                writeTextPDF(pdf, 'Number of webpages', arrImages.length);

                // add credit
                /*
                pdf.fontSize(10)
                    .fillColor('black')
                    .text(credit);
                */
            }

            // check page object returned from content collection
            if (page[0] !== undefined && pdfShooter.userPDFOptions.excludeMeta === false) {

                pdf.addPage({
                    size: [sizeOf(png).width, sizeOf(png).height],
                    width: sizeOf(png).width,
                    height: sizeOf(png).height,
                    margins: {
                        top: 72,
                        left: 60,
                        right: 72,
                        bottom: 72
                    }
                });

                // add page specific details
                writeTextPDF(pdf, 'URL', page[0].loc, 'blue');

                writeTextPDF(pdf, 'Meta Title', page[0].meta.title);

                writeTextPDF(pdf, 'Meta Description', page[0].meta.description);

            }
        }

        pdf.addPage({
            size: [sizeOf(png).width, sizeOf(png).height],
            width: sizeOf(png).width,
            height: sizeOf(png).height
        });

        pdf.image(png, 0, 0);
    });

    pdf.end();

    writeStream.on('finish', function() {

        done();

    });

}

PDFShooter.prototype.start = function() {

    console.log(chalk.yellow.bold('\n ⤷ Generating PDFs'));

    var deferred = Q.defer(),
        domainName = this.options.domain.name,
        outputPath = this.options.paths.output,
        pdfs = [],
        pdfShooter = this;

    // load array of viewports and their respective .png files
    pdfs = this.options.viewports.map(function(item, i) {
        return {
            'viewport': item.viewport,
            'dimensions': { 'width': item.width, 'height': item.height },
            'files': getFiles(outputPath, item.width)
        };
    });

    pdfShooter.website.get()

    .then(function() {

        // create individual pdfs based on viewport
        return asynEach(pdfs, function(item, i) {

            var done = this.async();

            // make sure we have some screenshots
            if (item.files.length > 0) {
                createPDF(pdfShooter, item.files, item.dimensions, domainName, outputPath, item.viewport, function() {
                    done();
                });
            } else {

                console.log(chalk.red.bold('   ✗ '), chalk.red('Screenshots for viewport '), chalk.red.bold(item.viewport), chalk.red(' do not exist.'));

                done();
            }

        }, function(success, array) {
            if (success) {
                deferred.resolve(array);

            } else {
                deferred.reject(success);
            }
        });

    });

    return deferred.promise;
};

module.exports = PDFShooter;
<commit_msg>fix: check for undefined meta data<commit_after>'use strict';

var asynEach = require('async-foreach').forEach,
    chalk = require('chalk'),
    fs = require('graceful-fs'),
    globby = require('globby'),
    path = require('path'),
    pkg = require('../package.json'),
    PDF = require('pdfkit'),
    Q = require('q'),
    sizeOf = require('image-size'),
    utils = require('./utils'),
    Website = require('./website');

/**
 * Public: Constructor for PDFShooter
 * @author Steven Britton
 * @date   2016-04-14
 * @param  {Object}  options [description]
 */
var PDFShooter = function(parameters) {

    utils.log.verbose('PDFShooter: constructor', parameters);

    this.options = parameters;
    this.userPDFOptions = parameters.pdf_options;
    this.website = new Website(this.options);

};

function getDate() {

    var d = new Date(),
        date =
        d.getUTCFullYear() + '-' +
        (d.getUTCMonth() + 1) + '-' +
        d.getUTCDate();
    return date;
}

/**
 * Gets all files based on viewport size
 * @author Steven Britton
 * @date   2016-04-26
 * @param  {String}   outputPath Path to generated screenshots
 * @param  {String}   size       Viewport size
 * @return {Array}               Array of paths to images
 */
function getFiles(outputPath, size) {
    return globby.sync([path.join(outputPath, '**', '*' + size + '.png')]);
}

/**
 * Write text to PDF object
 * @author Steven Britton
 * @date   2016-04-25
 * @param  {Object}   pdf        pdf-kit object
 * @param  {String}   title      title header for field
 * @param  {String}   field      field that's part of the cotent collection
 * @param  {String}   fieldColor font color for the field parameter
 */
function writeTextPDF(pdf, title, field, fieldColor) {

    fieldColor = fieldColor || 'black';
    field =  field !== undefined ? field : '';

    pdf.fontSize(30)
        .fillColor('black')
        .text(title);

    pdf.fontSize(20)
        .fillColor(fieldColor)
        .text(field);

    pdf.moveDown();
}

/**
 * Generates a PDF containing the passed array of images along with metadata collected from Siteshooter
 * @author Steven Britton
 * @date   2016-05-10
 * @param  {Object}   pdfShooter
 * @param  {Array}    arrImages
 * @param  {[type]}   dimensions
 * @param  {String}   domainName
 * @param  {String}   outputPath
 * @param  {[type]}   viewport
 * @param  {Function} done
 * @return {Function} callback
 */
function createPDF(pdfShooter, arrImages, dimensions, domainName, outputPath, viewport, done) {

    utils.log.verbose('createPDF', arguments);

    var date = getDate(),
        credit,
        page,
        pdfPath = path.resolve(outputPath, utils.urlToDir(domainName) + '-' + viewport + '-' + date + '.pdf'),
        tempURL,
        writeStream;

    utils.log.verbose('pdfPath', pdfPath);

    credit = 'Siteshooter v' + pkg.version + ' - https://www.devopsgroup.io';

    var pdf = new PDF({
        layout: 'portrait',
        margins: {
            top: 72,
            left: 60,
            right: 72,
            bottom: 20
        }
    });

    writeStream = fs.createWriteStream(pdfPath);

    pdf.pipe(writeStream);

    pdf.pipe(fs.createWriteStream(pdfPath));

    arrImages.forEach(function(png, i) {

        page = png.replace('screenshots/', '');

        // remove from last slash and remaining chars (e.g., /1600.png)
        page = page.substr(0, page.lastIndexOf('/'));

        // get page information stored in website collection
        page = pdfShooter.website.contentCollection.pages.filter(function(item, indx, arr) {

            // remove protocol and last slash
            tempURL = item.loc
                .replace(/^(http|https):\/\//, '')
                .replace(/\/$/, '');

            return (tempURL === page);
        });

        // do we have page specific data?
        if (Array.isArray(page) && page.length > 0) {

            // add site-level information to cover page
            if (i === 0) {

                writeTextPDF(pdf, 'Date', date);

                writeTextPDF(pdf, 'Website', page[0].loc, 'blue');

                writeTextPDF(pdf, 'Viewport', viewport + ' (' + dimensions.width + 'x' + dimensions.height + ')');

                writeTextPDF(pdf, 'Google Analytics Version', page[0].meta.gaVersion);

                writeTextPDF(pdf, 'Number of webpages', arrImages.length);

                // add credit
                /*
                pdf.fontSize(10)
                    .fillColor('black')
                    .text(credit);
                */
            }

            // check page object returned from content collection
            if (page[0] !== undefined && pdfShooter.userPDFOptions.excludeMeta === false) {

                pdf.addPage({
                    size: [sizeOf(png).width, sizeOf(png).height],
                    width: sizeOf(png).width,
                    height: sizeOf(png).height,
                    margins: {
                        top: 72,
                        left: 60,
                        right: 72,
                        bottom: 72
                    }
                });

                // add page specific details
                writeTextPDF(pdf, 'URL', page[0].loc, 'blue');

                writeTextPDF(pdf, 'Meta Title', page[0].meta.title);

                writeTextPDF(pdf, 'Meta Description', page[0].meta.description);

            }
        }

        pdf.addPage({
            size: [sizeOf(png).width, sizeOf(png).height],
            width: sizeOf(png).width,
            height: sizeOf(png).height
        });

        pdf.image(png, 0, 0);
    });

    pdf.end();

    writeStream.on('finish', function() {

        done();

    });

}

PDFShooter.prototype.start = function() {

    console.log(chalk.yellow.bold('\n ⤷ Generating PDFs'));

    var deferred = Q.defer(),
        domainName = this.options.domain.name,
        outputPath = this.options.paths.output,
        pdfs = [],
        pdfShooter = this;

    // load array of viewports and their respective .png files
    pdfs = this.options.viewports.map(function(item, i) {
        return {
            'viewport': item.viewport,
            'dimensions': { 'width': item.width, 'height': item.height },
            'files': getFiles(outputPath, item.width)
        };
    });

    pdfShooter.website.get()

    .then(function() {

        // create individual pdfs based on viewport
        return asynEach(pdfs, function(item, i) {

            var done = this.async();

            // make sure we have some screenshots
            if (item.files.length > 0) {
                createPDF(pdfShooter, item.files, item.dimensions, domainName, outputPath, item.viewport, function() {
                    done();
                });
            } else {

                console.log(chalk.red.bold('   ✗ '), chalk.red('Screenshots for viewport '), chalk.red.bold(item.viewport), chalk.red(' do not exist.'));

                done();
            }

        }, function(success, array) {
            if (success) {
                deferred.resolve(array);

            } else {
                deferred.reject(success);
            }
        });

    });

    return deferred.promise;
};

module.exports = PDFShooter;
<|endoftext|>


<commit_before>'use strict';

/* create.js
 * Create new tree components through RPV lib.
 */

module.exports = create;

function create(constructor, prototype){
  let Component = function(){
    // jshint ignore: start
    if (!new.target) return new NewComponent(...arguments);
    // jshint ignore: end

    this.tags = [];
    constructor.apply(this, arguments);
  };

  Component.prototype = prototype;

  Component.prototype.has = function(item){
    let test = this.tags.indexOf(item);
    return test !== -1 ? true : null;
  };

  Component.prototype.set = function(){
    this.tags.push(...arguments);
    return this;
  };

  return Component;
}
<commit_msg>Add unset<commit_after>'use strict';

/* create.js
 * Create new tree components through RPV lib.
 */

module.exports = create;

function create(constructor, prototype){
  let Component = function(){
    // jshint ignore: start
    if (!new.target) return new NewComponent(...arguments);
    // jshint ignore: end

    this.tags = [];
    constructor.apply(this, arguments);
  };

  Component.prototype = prototype;

  Component.prototype.has = function(item){
    let test = this.tags.indexOf(item);
    return test !== -1 ? true : null;
  };

  Component.prototype.set = function(){
    this.tags.push(...arguments);
    return this;
  };

  Component.prototype.unset = function(item){
    this.tags = this.tags.splice(this.tags.indexOf(item), 1);
  };

  return Component;
}
<|endoftext|>


<commit_before>/*

    create.js

*/

var fs      = require('fs'),
    config  = require('config');

module.exports = function(emitter, htmls) {

}<commit_msg>remove dead file<commit_after><|endoftext|>


<commit_before>/**
 * Handle every persistence-related task
 * The interface Datastore expects to be implemented is
 * * Persistence.loadDatabase(callback) and callback has signature err
 * * Persistence.persistNewState(newDocs, callback) where newDocs is an array of documents and callback has signature err
 */

var fs = require('fs')
  , path = require('path')
  , model = require('./model')
  , async = require('async')
  , mkdirp = require('mkdirp')
  , customUtils = require('./customUtils')
  , Index = require('./indexes')
  ;

/**
 * Create a new Persistence object for database options.db
 * @param {Datastore} options.db
 * @param {Boolean} options.nodeWebkitAppName Optional, specify the name of your NW app if you want options.filename to be relative to the directory where
 *                                            Node Webkit stores application data such as cookies and local storage (the best place to store data in my opinion)
 */
function Persistence (options) {
  this.db = options.db;
  this.inMemoryOnly = this.db.inMemoryOnly;
  this.filename = this.db.filename;

  if (!this.inMemoryOnly && this.filename) {
    if (this.filename.charAt(this.filename.length - 1) === '~') {
      throw "The datafile name can't end with a ~, which is reserved for automatic backup files";
    } else {
      this.tempFilename = this.filename + '~';
      this.oldFilename = this.filename + '~~';
    }
  }

  // For NW apps, store data in the same directory where NW stores application data
  if (this.filename && options.nodeWebkitAppName) {
    console.log("==================================================================");
    console.log("WARNING: The nodeWebkitAppName option is deprecated");
    console.log("To get the path to the directory where Node Webkit stores the data");
    console.log("for your app, use the internal nw.gui module like this");
    console.log("require('nw.gui').App.dataPath");
    console.log("See https://github.com/rogerwang/node-webkit/issues/500");
    console.log("==================================================================");
    this.filename = Persistence.getNWAppFilename(options.nodeWebkitAppName, this.filename);
    this.tempFilename = Persistence.getNWAppFilename(options.nodeWebkitAppName, this.tempFilename);
    this.oldFilename = Persistence.getNWAppFilename(options.nodeWebkitAppName, this.oldFilename);
  }
};

/**
 * Check if a directory exists and create it on the fly if it is not the case
 * cb is optional, signature: err
 */
Persistence.ensureDirectoryExists = function (dir, cb) {
  var callback = cb || function () {}
    ;

  mkdirp(dir, function (err) { return callback(err); });
};

/**
 * Return the path the datafile if the given filename is relative to the directory where Node Webkit stores
 * data for this application. Probably the best place to store data
 */
Persistence.getNWAppFilename = function (appName, relativeFilename) {
  var home;

  switch (process.platform) {
    case 'win32':
    case 'win64':
      home = process.env.LOCALAPPDATA || process.env.APPDATA;
      if (!home) { throw "Couldn't find the base application data folder"; }
      home = path.join(home, appName);
      break;
    case 'darwin':
      home = process.env.HOME;
      if (!home) { throw "Couldn't find the base application data directory"; }
      home = path.join(home, 'Library', 'Application Support', appName);
      break;
    case 'linux':
      home = process.env.HOME;
      if (!home) { throw "Couldn't find the base application data directory"; }
      home = path.join(home, '.config', appName);
      break;
    default:
      throw "Can't use the Node Webkit relative path for platform " + process.platform;
      break;
  }

  return path.join(home, 'nedb-data', relativeFilename);
}

/**
 * Persist cached database
 * This serves as a compaction function since the cache always contains only the number of documents in the collection
 * while the data file is append-only so it may grow larger
 * @param {Function} cb Optional callback, signature: err
 */
Persistence.prototype.persistCachedDatabase = function (cb) {
  var callback = cb || function () {}
    , toPersist = ''
    , self = this
    ;

  if (this.inMemoryOnly) { return callback(null); }

  this.db.getAllData().forEach(function (doc) {
    toPersist += model.serialize(doc) + '\n';
  });
  Object.keys(this.db.indexes).forEach(function (fieldName) {
    if (fieldName != "_id") {   // The special _id index is managed by datastore.js, the others need to be persisted
      toPersist += model.serialize({ $$indexCreated: { fieldName: fieldName, unique: self.db.indexes[fieldName].unique, sparse: self.db.indexes[fieldName].sparse }}) + '\n';
    }
  });

  async.waterfall([
    async.apply(customUtils.ensureFileDoesntExist, self.tempFilename)
  , async.apply(customUtils.ensureFileDoesntExist, self.oldFilename)
  , function (cb) {
      fs.exists(self.filename, function (exists) {
        if (exists) {
          fs.rename(self.filename, self.oldFilename, function (err) { return cb(err); });
        } else {
          return cb();
        }
      });
  }
  , function (cb) {
      fs.writeFile(self.tempFilename, toPersist, function (err) { return cb(err); });
    }
  , function (cb) {
      fs.rename(self.tempFilename, self.filename, function (err) { return cb(err); });
    }
  , async.apply(customUtils.ensureFileDoesntExist, self.oldFilename)
  ], function (err) { if (err) { return callback(err); } else { return callback(null); } })
};

/**
 * Queue a rewrite of the datafile
 */
Persistence.prototype.compactDatafile = function () {
  this.db.executor.push({ this: this, fn: this.persistCachedDatabase, arguments: [] });
};

/**
 * Set automatic compaction every interval ms
 * @param {Number} interval in milliseconds, with an enforced minimum of 5 seconds
 */
Persistence.prototype.setAutocompactionInterval = function (interval) {
  var self = this
    , minInterval = 5000
    , realInterval = Math.max(interval||0, minInterval)
    ;

  this.stopAutocompaction();

  this.autocompactionIntervalId = setInterval(function () {
    self.compactDatafile();
  }, realInterval);
};

/**
 * Stop autocompaction (do nothing if autocompaction was not running)
 */
Persistence.prototype.stopAutocompaction = function () {
  if (this.autocompactionIntervalId) { clearInterval(this.autocompactionIntervalId); }
};

/**
 * Persist new state for the given newDocs (can be insertion, update or removal)
 * Use an append-only format
 * @param {Array} newDocs Can be empty if no doc was updated/removed
 * @param {Function} cb Optional, signature: err
 */
Persistence.prototype.persistNewState = function (newDocs, cb) {
  var self = this
    , toPersist = ''
    , callback = cb || function () {}
    ;

  // In-memory only datastore
  if (self.inMemoryOnly) { return callback(null); }

  newDocs.forEach(function (doc) {
    toPersist += model.serialize(doc) + '\n';
  });

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

  fs.appendFile(self.filename, toPersist, 'utf8', function (err) {
    return callback(err);
  });
};

/**
 * From a database's raw data, return the corresponding
 * machine understandable collection
 */
Persistence.treatRawData = function (rawData) {
  var data = rawData.split('\n')
    , dataById = {}
    , tdata = []
    , i
    , indexes = {}
    ;

  for (i = 0; i < data.length; i += 1) {
    var doc;

    try {
      doc = model.deserialize(data[i]);
      if (doc._id) {
        if (doc.$$deleted === true) {
          delete dataById[doc._id];
        } else {
          dataById[doc._id] = doc;
        }
      } else if (doc.$$indexCreated && doc.$$indexCreated.fieldName != undefined) {
        indexes[doc.$$indexCreated.fieldName] = doc.$$indexCreated;
      } else if (typeof doc.$$indexRemoved === "string") {
        delete indexes[doc.$$indexRemoved];
      }
    } catch (e) {
    }
  }

  Object.keys(dataById).forEach(function (k) {
    tdata.push(dataById[k]);
  });

  return { data: tdata, indexes: indexes };
};

/**
 * Ensure that this.filename contains the most up-to-date version of the data
 * Even if a loadDatabase crashed before
 */
Persistence.prototype.ensureDatafileIntegrity = function (callback) {
  var self = this  ;

  fs.exists(self.filename, function (filenameExists) {
    // Write was successful
    if (filenameExists) { return callback(null); }

    fs.exists(self.oldFilename, function (oldFilenameExists) {
      // New database
      if (!oldFilenameExists) {
        return fs.writeFile(self.filename, '', 'utf8', function (err) { callback(err); });
      }

      // Write failed, use old version
      fs.rename(self.oldFilename, self.filename, function (err) { return callback(err); });
    });
  });
};

/**
 * Load the database
 * 1) Create all indexes
 * 2) Insert all data
 * 3) Compact the database
 * This means pulling data out of the data file or creating it if it doesn't exist
 * Also, all data is persisted right away, which has the effect of compacting the database file
 * This operation is very quick at startup for a big collection (60ms for ~10k docs)
 * @param {Function} cb Optional callback, signature: err
 */
Persistence.prototype.loadDatabase = function (cb) {
  var callback = cb || function () {}
    , self = this
    ;

  self.db.resetIndexes();

  // In-memory only datastore
  if (self.inMemoryOnly) { return callback(null); }

  async.waterfall([
    function (cb) {
      Persistence.ensureDirectoryExists(path.dirname(self.filename), function (err) {
        self.ensureDatafileIntegrity(function (exists) {
          fs.readFile(self.filename, 'utf8', function (err, rawData) {
            if (err) { return cb(err); }
            var treatedData = Persistence.treatRawData(rawData);

            // Recreate all indexes in the datafile
            Object.keys(treatedData.indexes).forEach(function (key) {
              self.db.indexes[key] = new Index(treatedData.indexes[key]);
            });

            // Fill cached database (i.e. all indexes) with data
            try {
              self.db.resetIndexes(treatedData.data);
            } catch (e) {
              self.db.resetIndexes();   // Rollback any index which didn't fail
              return cb(e);
            }

            self.db.persistence.persistCachedDatabase(cb);
          });
        });
      });
    }
  ], function (err) {
       if (err) { return callback(err); }

       self.db.executor.processBuffer();
       return callback(null);
     });
};

// Interface
module.exports = Persistence;
<commit_msg>Some styling<commit_after>/**
 * Handle every persistence-related task
 * The interface Datastore expects to be implemented is
 * * Persistence.loadDatabase(callback) and callback has signature err
 * * Persistence.persistNewState(newDocs, callback) where newDocs is an array of documents and callback has signature err
 */

var fs = require('fs')
  , path = require('path')
  , model = require('./model')
  , async = require('async')
  , mkdirp = require('mkdirp')
  , customUtils = require('./customUtils')
  , Index = require('./indexes')
  ;

/**
 * Create a new Persistence object for database options.db
 * @param {Datastore} options.db
 * @param {Boolean} options.nodeWebkitAppName Optional, specify the name of your NW app if you want options.filename to be relative to the directory where
 *                                            Node Webkit stores application data such as cookies and local storage (the best place to store data in my opinion)
 */
function Persistence (options) {
  this.db = options.db;
  this.inMemoryOnly = this.db.inMemoryOnly;
  this.filename = this.db.filename;

  if (!this.inMemoryOnly && this.filename) {
    if (this.filename.charAt(this.filename.length - 1) === '~') {
      throw "The datafile name can't end with a ~, which is reserved for automatic backup files";
    } else {
      this.tempFilename = this.filename + '~';
      this.oldFilename = this.filename + '~~';
    }
  }

  // For NW apps, store data in the same directory where NW stores application data
  if (this.filename && options.nodeWebkitAppName) {
    console.log("==================================================================");
    console.log("WARNING: The nodeWebkitAppName option is deprecated");
    console.log("To get the path to the directory where Node Webkit stores the data");
    console.log("for your app, use the internal nw.gui module like this");
    console.log("require('nw.gui').App.dataPath");
    console.log("See https://github.com/rogerwang/node-webkit/issues/500");
    console.log("==================================================================");
    this.filename = Persistence.getNWAppFilename(options.nodeWebkitAppName, this.filename);
    this.tempFilename = Persistence.getNWAppFilename(options.nodeWebkitAppName, this.tempFilename);
    this.oldFilename = Persistence.getNWAppFilename(options.nodeWebkitAppName, this.oldFilename);
  }
};

/**
 * Check if a directory exists and create it on the fly if it is not the case
 * cb is optional, signature: err
 */
Persistence.ensureDirectoryExists = function (dir, cb) {
  var callback = cb || function () {}
    ;

  mkdirp(dir, function (err) { return callback(err); });
};

/**
 * Return the path the datafile if the given filename is relative to the directory where Node Webkit stores
 * data for this application. Probably the best place to store data
 */
Persistence.getNWAppFilename = function (appName, relativeFilename) {
  var home;

  switch (process.platform) {
    case 'win32':
    case 'win64':
      home = process.env.LOCALAPPDATA || process.env.APPDATA;
      if (!home) { throw "Couldn't find the base application data folder"; }
      home = path.join(home, appName);
      break;
    case 'darwin':
      home = process.env.HOME;
      if (!home) { throw "Couldn't find the base application data directory"; }
      home = path.join(home, 'Library', 'Application Support', appName);
      break;
    case 'linux':
      home = process.env.HOME;
      if (!home) { throw "Couldn't find the base application data directory"; }
      home = path.join(home, '.config', appName);
      break;
    default:
      throw "Can't use the Node Webkit relative path for platform " + process.platform;
      break;
  }

  return path.join(home, 'nedb-data', relativeFilename);
}

/**
 * Persist cached database
 * This serves as a compaction function since the cache always contains only the number of documents in the collection
 * while the data file is append-only so it may grow larger
 * @param {Function} cb Optional callback, signature: err
 */
Persistence.prototype.persistCachedDatabase = function (cb) {
  var callback = cb || function () {}
    , toPersist = ''
    , self = this
    ;

  if (this.inMemoryOnly) { return callback(null); }

  this.db.getAllData().forEach(function (doc) {
    toPersist += model.serialize(doc) + '\n';
  });
  Object.keys(this.db.indexes).forEach(function (fieldName) {
    if (fieldName != "_id") {   // The special _id index is managed by datastore.js, the others need to be persisted
      toPersist += model.serialize({ $$indexCreated: { fieldName: fieldName, unique: self.db.indexes[fieldName].unique, sparse: self.db.indexes[fieldName].sparse }}) + '\n';
    }
  });

  async.waterfall([
    async.apply(customUtils.ensureFileDoesntExist, self.tempFilename)
  , async.apply(customUtils.ensureFileDoesntExist, self.oldFilename)
  , function (cb) {
      fs.exists(self.filename, function (exists) {
        if (exists) {
          fs.rename(self.filename, self.oldFilename, function (err) { return cb(err); });
        } else {
          return cb();
        }
      });
  }
  , function (cb) {
      fs.writeFile(self.tempFilename, toPersist, function (err) { return cb(err); });
    }
  , function (cb) {
      fs.rename(self.tempFilename, self.filename, function (err) { return cb(err); });
    }
  , async.apply(customUtils.ensureFileDoesntExist, self.oldFilename)
  ], function (err) { if (err) { return callback(err); } else { return callback(null); } })
};

/**
 * Queue a rewrite of the datafile
 */
Persistence.prototype.compactDatafile = function () {
  this.db.executor.push({ this: this, fn: this.persistCachedDatabase, arguments: [] });
};

/**
 * Set automatic compaction every interval ms
 * @param {Number} interval in milliseconds, with an enforced minimum of 5 seconds
 */
Persistence.prototype.setAutocompactionInterval = function (interval) {
  var self = this
    , minInterval = 5000
    , realInterval = Math.max(interval || 0, minInterval)
    ;

  this.stopAutocompaction();

  this.autocompactionIntervalId = setInterval(function () {
    self.compactDatafile();
  }, realInterval);
};

/**
 * Stop autocompaction (do nothing if autocompaction was not running)
 */
Persistence.prototype.stopAutocompaction = function () {
  if (this.autocompactionIntervalId) { clearInterval(this.autocompactionIntervalId); }
};

/**
 * Persist new state for the given newDocs (can be insertion, update or removal)
 * Use an append-only format
 * @param {Array} newDocs Can be empty if no doc was updated/removed
 * @param {Function} cb Optional, signature: err
 */
Persistence.prototype.persistNewState = function (newDocs, cb) {
  var self = this
    , toPersist = ''
    , callback = cb || function () {}
    ;

  // In-memory only datastore
  if (self.inMemoryOnly) { return callback(null); }

  newDocs.forEach(function (doc) {
    toPersist += model.serialize(doc) + '\n';
  });

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

  fs.appendFile(self.filename, toPersist, 'utf8', function (err) {
    return callback(err);
  });
};

/**
 * From a database's raw data, return the corresponding
 * machine understandable collection
 */
Persistence.treatRawData = function (rawData) {
  var data = rawData.split('\n')
    , dataById = {}
    , tdata = []
    , i
    , indexes = {}
    ;

  for (i = 0; i < data.length; i += 1) {
    var doc;

    try {
      doc = model.deserialize(data[i]);
      if (doc._id) {
        if (doc.$$deleted === true) {
          delete dataById[doc._id];
        } else {
          dataById[doc._id] = doc;
        }
      } else if (doc.$$indexCreated && doc.$$indexCreated.fieldName != undefined) {
        indexes[doc.$$indexCreated.fieldName] = doc.$$indexCreated;
      } else if (typeof doc.$$indexRemoved === "string") {
        delete indexes[doc.$$indexRemoved];
      }
    } catch (e) {
    }
  }

  Object.keys(dataById).forEach(function (k) {
    tdata.push(dataById[k]);
  });

  return { data: tdata, indexes: indexes };
};

/**
 * Ensure that this.filename contains the most up-to-date version of the data
 * Even if a loadDatabase crashed before
 */
Persistence.prototype.ensureDatafileIntegrity = function (callback) {
  var self = this  ;

  fs.exists(self.filename, function (filenameExists) {
    // Write was successful
    if (filenameExists) { return callback(null); }

    fs.exists(self.oldFilename, function (oldFilenameExists) {
      // New database
      if (!oldFilenameExists) {
        return fs.writeFile(self.filename, '', 'utf8', function (err) { callback(err); });
      }

      // Write failed, use old version
      fs.rename(self.oldFilename, self.filename, function (err) { return callback(err); });
    });
  });
};

/**
 * Load the database
 * 1) Create all indexes
 * 2) Insert all data
 * 3) Compact the database
 * This means pulling data out of the data file or creating it if it doesn't exist
 * Also, all data is persisted right away, which has the effect of compacting the database file
 * This operation is very quick at startup for a big collection (60ms for ~10k docs)
 * @param {Function} cb Optional callback, signature: err
 */
Persistence.prototype.loadDatabase = function (cb) {
  var callback = cb || function () {}
    , self = this
    ;

  self.db.resetIndexes();

  // In-memory only datastore
  if (self.inMemoryOnly) { return callback(null); }

  async.waterfall([
    function (cb) {
      Persistence.ensureDirectoryExists(path.dirname(self.filename), function (err) {
        self.ensureDatafileIntegrity(function (exists) {
          fs.readFile(self.filename, 'utf8', function (err, rawData) {
            if (err) { return cb(err); }
            var treatedData = Persistence.treatRawData(rawData);

            // Recreate all indexes in the datafile
            Object.keys(treatedData.indexes).forEach(function (key) {
              self.db.indexes[key] = new Index(treatedData.indexes[key]);
            });

            // Fill cached database (i.e. all indexes) with data
            try {
              self.db.resetIndexes(treatedData.data);
            } catch (e) {
              self.db.resetIndexes();   // Rollback any index which didn't fail
              return cb(e);
            }

            self.db.persistence.persistCachedDatabase(cb);
          });
        });
      });
    }
  ], function (err) {
       if (err) { return callback(err); }

       self.db.executor.processBuffer();
       return callback(null);
     });
};

// Interface
module.exports = Persistence;
<|endoftext|>


<commit_before>define([
  'views/base/view',
  'text!templates/car_item.hbs',
  'models/car'
], function(View, template, Car) {
  'use strict';

  var CarItemView = View.extend({

    template: template,

    tagName: 'li',
    className: 'car-item',

    listen: {
      'change model': 'render'
    },
    events: {
      'click .delete': 'delete'
    },

    'delete': function (event) {
      event.preventDefault();
      var model = this.model;
      var collection = model.collection;
      // Remove the model from its collection. This disposes this item view.
      collection.remove(model);
      // Dispose the model explicitly. It shouldn’t be used elsewhere.
      model.dispose();
      collection.save();
    }

  });

  return CarItemView;
});
<commit_msg>Remove unused dependency.<commit_after>define([
  'views/base/view',
  'text!templates/car_item.hbs'
], function(View, template) {
  'use strict';

  var CarItemView = View.extend({

    template: template,

    tagName: 'li',
    className: 'car-item',

    listen: {
      'change model': 'render'
    },
    events: {
      'click .delete': 'delete'
    },

    'delete': function (event) {
      event.preventDefault();
      // Shortcuts
      var model = this.model;
      var collection = model.collection;
      // Remove the model from its collection. This disposes this item view.
      collection.remove(model);
      // Dispose the model explicitly. It shouldn’t be used elsewhere.
      model.dispose();
      collection.save();
    }

  });

  return CarItemView;
});
<|endoftext|>


<commit_before>window.urb = {
  ship: ship,
  port: port,
  auto: auto,
  oryx: oryx,
  user: user,
  seqn_h: 0,
  seqn_u: 0,
  seqn_s: 0,
  dely: 0,
  puls: 0,
  perms: {
    pol:"gie",
    sub:"tis",
    uns:"tiu",
    mes:"tim",
    heb:"tih"
  },
  cabs: {},

  req: function(method,url,params,json,cb) {
    var xhr = new XMLHttpRequest()
    xhr.open(method.toUpperCase(), url)
    if(json)
      xhr.setRequestHeader("content-type", "text/json")

    _data = {}
    if(params.data) { _data.data = params.data; }
    if(params.ship) { _data.ship = params.ship; }
    if(params.path) { _data.path = params.path; }
    if(params.appl) { _data.appl = params.appl; }
    __data = {oryx: oryx, xyro: _data}
    xhr.send(JSON.stringify(__data))

    if(cb) {
      xhr.onload = function() {
        cb(null,{
          "status":this.status,
          "data":JSON.parse(this.responseText)
        })
      }
      xhr.onerror = function() {
        cb({
          "status":this.status,
          "data":this.responseText
        })
      }
    }
  },

  send: function(params,cb) {
    if(!params)
      throw new Error("You must supply params to urb.send.")
    if(!params.appl)
      throw new Error("You must specify an appl for urb.send.")
    if(!params.data) { params.data = {}; }

    var method, perm, url, $this

    type = params.type ? params.type : "mes"
    perm = this.perms[type]

    params.ship = params.ship ? params.ship : this.ship

    method = "put"
    url = [perm,this.user,this.port,this.seqn_s]
    url = "/"+url.join("/")

    this.seqn_s++

    $this = this
    this.req(method,url,params,true,function(err,data) {
      if(err) { $this.seqn_s--; }
      if(cb) { cb.apply(this,arguments); }
    })
  },

  subscribe: function(appl,path,ship,cb) {
    if(!cb)
      throw new Error("You must supply a callback to urb.subscribe.")

    var method, perm, url, $this

    ship = ship ? ship : this.ship

    method = "get"
    params = {
      type:"sub",
      appl:appl,
      path:path,
      ship:ship,
      incs:function() {
        window.urb.seqn_u++
      }
    }

    this.cabs[appl+","+path.replace(/[^\x00-\x7F]/g, "")+","+ship] = cb

    type = params.type ? params.type : "mes"
    perm = this.perms[type]
    method = "put"
    url = [perm,this.user,this.port]
    url = "/"+url.join("/")

    $this = this
    this.req(method,url,params,true,function(err,data) {
      if(cb) { cb.call(this,err,{status: data.status, data: data.data.data})}
      if(!err && $this.puls == 0) {
        params.type = "pol"
        $this.poll(params)
      }
    })
  },

  unsubscribe: function(appl,path,ship,cb) {
    method = "put"
    type = "uns"
    perm = this.perms[type]
    url = [perm,this.user,this.port]
    url = "/"+url.join("/")

    params = {
      appl: appl,
      path: path,
      ship: ship
    }

    var $this = this
    this.req(method,url,params,true,function(err,data) {
      console.log('ok unsubscribed')
      fn = appl+","+path.replace(/[^\x00-\x7F]/g, "")+","+ship
      $this.cabs[fn]('subscription closed')
    })
  },

  heartbeat: function() {
    this.poll({
      type:"heb",
      ship:this.ship,
      incs:function() {
        window.urb.seqn_h++
      }
    },function() {
      console.log('heartbeat.')
    })
  },

  poll: function(params,cb) {
    if(!params)
      throw new Error("You must supply params to urb.poll.")

    var method, perm, url, $this

    method = "get"
    type = params.type ? params.type : "pol"
    perm = this.perms[type]
    url = [perm,this.user,this.port,this.seqn_u]
    url = "/"+url.join("/")

    this.puls = 1

    $this = this
    this.req(method,url,params,false,function(err,data) {
      if(cb) {
        cb.call(this,err,{status: data.status, data: data.data.data})
      } else {
        fn = data.data.appl+","+data.data.path.replace(/[^\x00-\x7F]/g, "")
            +","+data.data.ship
        $this.cabs[fn].call(this,err,
          {status: data.status, data: data.data.data})
      }

      if(err)
        $this.dely += 1000
      else {
        $this.dely = 0
        params.incs()
      }

      setTimeout(function() {
        $this.poll(params,cb)
      },$this.dely)
    })
  }
}
<commit_msg>preened urb.js<commit_after>window.urb = {
  ship: ship,
  port: port,
  auto: auto,
  oryx: oryx,
  user: user,
  seqn_h: 0,
  seqn_u: 0,
  seqn_s: 0,
  dely: 0,
  puls: 0,
  perms: {
    pol:"gie",
    sub:"tis",
    uns:"tiu",
    mes:"tim",
    heb:"tih"
  },
  cabs: {},

  req: function(method,url,params,json,cb) {
    var xhr = new XMLHttpRequest()
    xhr.open(method.toUpperCase(), url)
    if(json)
      xhr.setRequestHeader("content-type", "text/json")

    _data = {}
    if(params.data) { _data.data = params.data; }
    if(params.ship) { _data.ship = params.ship; }
    if(params.path) { _data.path = params.path; }
    if(params.appl) { _data.appl = params.appl; }
    __data = {oryx: oryx, xyro: _data}
    xhr.send(JSON.stringify(__data))

    if(cb) {
      xhr.onload = function() {
        cb(null,{
          "status":this.status,
          "data":JSON.parse(this.responseText)
        })
      }
      xhr.onerror = function() {
        cb({
          "status":this.status,
          "data":this.responseText
        })
      }
    }
  },

  send: function(params,cb) {
    if(!params)
      throw new Error("You must supply params to urb.send.")
    if(!params.appl)
      throw new Error("You must specify an appl for urb.send.")
    if(!params.data) { params.data = {}; }

    var method, perm, url, $this

    type = params.type ? params.type : "mes"
    perm = this.perms[type]

    params.ship = params.ship ? params.ship : this.ship

    method = "put"
    url = [perm,this.user,this.port,this.seqn_s]
    url = "/"+url.join("/")

    this.seqn_s++

    $this = this
    this.req(method,url,params,true,function(err,data) {
      if(err) { $this.seqn_s--; }
      if(cb) { cb.apply(this,arguments); }
    })
  },

  subscribe: function(params,cb) {
    if(!cb)
      throw new Error("You must supply a callback to urb.subscribe.")
    if(!params)
      throw new Error("You must supply params to urb.subscribe.")
    if(!params.appl)
      throw new Error("You must specify an appl for urb.subscribe.")
    if(!params.path)
      throw new Error("You must specify a path for urb.subscribe.")
    params.ship = params.ship ? params.ship : this.ship

    var method, perm, url, $this

    params.type = "sub"
    params.incs = function() {
        window.urb.seqn_u++
      }

    this.cabs[params.appl+","+params.path.replace(/[^\x00-\x7F]/g, "")+","+params.ship] = cb

    url = [this.perms["sub"],this.user,this.port]
    url = "/"+url.join("/")
    method = "put"

    $this = this
    this.req(method,url,params,true,function(err,data) {
      if(cb) { cb.call(this,err,{status: data.status, data: data.data.data})}
      if(!err && $this.puls == 0) {
        params.type = "pol"
        $this.poll(params)
      }
    })
  },

  unsubscribe: function(params,cb) {
    if(!params)
      throw new Error("You must supply params to urb.unsubscribe.")
    if(!params.appl)
      throw new Error("You must specify an appl for urb.unsubscribe.")
    if(!params.path)
      throw new Error("You must specify a path for urb.unsubscribe.")
    params.ship = params.ship ? params.ship : this.ship

    method = "put"
    type = "uns"
    url = [this.perms[type],this.user,this.port]
    url = "/"+url.join("/")

    var $this = this
    this.req(method,url,params,true,function(err,data) {
      fn = params.appl+","+params.path.replace(/[^\x00-\x7F]/g, "")+","+params.ship
      $this.cabs[fn]('subscription closed')
    })
  },

  heartbeat: function() {
    this.poll({
      type:"heb",
      ship:this.ship,
      incs:function() {
        window.urb.seqn_h++
      }
    },function() {
      console.log('heartbeat.')
    })
  },

  poll: function(params,cb) {
    if(!params)
      throw new Error("You must supply params to urb.poll.")

    var method, perm, url, $this

    method = "get"
    type = params.type ? params.type : "pol"
    perm = this.perms[type]
    url = [perm,this.user,this.port,this.seqn_u]
    url = "/"+url.join("/")

    this.puls = 1

    $this = this
    this.req(method,url,params,false,function(err,data) {
      if(cb) {
        cb.call(this,err,{status: data.status, data: data.data.data})
      } else {
        fn = data.data.appl+","+data.data.path.replace(/[^\x00-\x7F]/g, "")
            +","+data.data.ship
        $this.cabs[fn].call(this,err,
          {status: data.status, data: data.data.data})
      }

      if(err)
        $this.dely += 1000
      else {
        $this.dely = 0
        params.incs()
      }

      setTimeout(function() {
        $this.poll(params,cb)
      },$this.dely)
    })
  }
}
<|endoftext|>


<commit_before>var request = require('request');
var htmlparser = require("htmlparser");
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var parseUrl = require('url').parse;
var formatUrl = require('url').format;
var resolveUrl = require('url').resolve;
var querystring = require('querystring');

/**
 * Set this if you want fallback for sites that don't provide oEmbed.
 */
exports.EMBEDLY_KEY = undefined;
exports.EMBEDLY_URL = "http://api.embed.ly/1/oembed";

/**
 * htmlparser is not doing that for us :-(
 */
function expandEntities(s) {
    if (typeof s !== 'string')
	return undefined;
    return s.replace('&', '&').
	replace('<', '<').
	replace('>', '>').
	replace('"', '"').
	replace(''', '\'');
}

function getElementText(el) {
    if (el.children)
	return expandEntities(el.children.map(getElementText).join(""));
    else if (el.type === 'text')
	return expandEntities(el.data);
    else
	return "";
}

/**
 * Wraps request()
 */
function httpGet(url, cb) {
    try {
	return request({ uri: url,
			 jar: false,  /* no cookies */
			 headers: {
			     "User-Agent": exports.USER_AGENT
			 }
		       }, cb);
    } catch (e) {
	/* request() throws at us */
	if (cb)
	    cb(e);
	else
	    throw e;
    }
}

var package = require('../package.json');
exports.USER_AGENT = "node-oembed/" + package.version + " (" + package.homepage + ")";

/**
 * Write stream that collects <link rel='alternate'> @href by @type
 *
 * @param {Function} urlConvert Expands relative to absolute URL
 * @param {Function} cb Final callback
 */
function Discovery(urlConvert, cb) {
    this.urlConvert = urlConvert;
    this.callback = cb;
    var handler = new DiscoveryHandler(this);
    this.parser = new htmlparser.Parser(handler);
    this.alternates = {};
}
util.inherits(Discovery, EventEmitter);

Discovery.prototype.write = function(data) {
    this.parser.parseChunk(data);
    return true;
};

Discovery.prototype.end = function() {
    this.parser.done();
};

Discovery.prototype.addAlternate = function(type, href) {
    if (!this.alternates.hasOwnProperty(type))
	this.alternates[type] = this.urlConvert(href);
};

Discovery.prototype.onDone = function() {
    this.callback(null, this.alternates);
};

/**
 * Methods implement htmlparser handler interface
 *
 * @param {Discovery} disco
 */
function DiscoveryHandler(disco) {
    this.disco = disco;
}

DiscoveryHandler.prototype = {
    reset: function() {
    },
    done: function() {
	this.disco.onDone();
    },
    writeTag: function(el) {
	var attrs = el.attribs;
	var type = attrs && expandEntities(attrs.type);
	var href = attrs && expandEntities(attrs.href);
	if (el.name === 'link' &&
	    attrs.rel === 'alternate' &&
	    type && href) {
	    this.disco.addAlternate(type, href);
	}
    },
    writeText: function() {
    },
    writeComment: function() {
    },
    writeDirective: function() {
    }
};

exports.discover = function(url, cb) {
    var req;
    try {
	req = httpGet(url);
    } catch (e) {
	return cb(e);
    }

    req.on('response', function(res) {
	if (res.statusCode === 200) {
	    var disco = new Discovery(function(href) {
		return resolveUrl(url, href);
	    }, cb);
	    util.pump(res, disco);
	} else
	    cb(new Error("HTTP status " + res.statusCode));
    });
    req.on('error', function(error) {
	cb(error);
    });
};

function OEmbedXMLParser(cb) {
    this.callback = cb;
    var handler = new htmlparser.DefaultHandler(function(error, dom) {
	var oembedRoot = dom &&
	    dom.filter(function(el) {
		return el.name === 'oembed';
	    })[0];
	if (oembedRoot && oembedRoot.children) {
	    var result = {};
	    oembedRoot.children.forEach(function(child) {
		if (child.name)
		    result[child.name] = getElementText(child);
	    });
	    cb(null, result);
	} else
	    cb(error || new Error("Invalid oEmbed document"));
    });
    this.parser = new htmlparser.Parser(handler);
    this.alternates = {};
}
util.inherits(OEmbedXMLParser, EventEmitter);

OEmbedXMLParser.prototype.write = function(data) {
    this.parser.parseChunk(data);
    return true;
};

OEmbedXMLParser.prototype.end = function() {
    this.parser.done();
};

exports.fetchXML = function(url, cb) {
    var req = httpGet(url);
    req.on('response', function(res) {
	if (res.statusCode === 200) {
	    var parser = new OEmbedXMLParser(cb);
	    util.pump(res, parser);
	} else
	    cb(new Error("HTTP status " + res.statusCode));
    });
    req.on('error', function(error) {
	cb(error);
    });
};

exports.fetchJSON = function(url, cb) {
    httpGet(url, function(error, res, body) {
	if (!error &&
	    res.statusCode === 200 &&
	    body) {
	    try {
		cb(null, JSON.parse(body));
	    } catch (e) {
		cb(e);
	    }
	} else
	    cb(error || new Error("HTTP status " + res.statusCode));
    });
};

var MIME_OEMBED_JSON = exports.MIME_OEMBED_JSON = 'application/json+oembed';
var MIME_OEMBED_XML = exports.MIME_OEMBED_XML = 'text/xml+oembed';
var ALLOWED_PARAMETERS = ["url", "format", "key", "maxwidth", "maxheight"];

function applyParameters(url, parameters) {
    if (!parameters)
	/* Nothing to do, skip */
	return url;

    if (!url.query || typeof url.query === 'string')
	/* Parse querystring */
	url = parseUrl(url, true);

    for(var k in parameters)
	if (ALLOWED_PARAMETERS.indexOf(k) >= 0)
	    url.query[k] = parameters[k];

    /* request assumes .search, not .query */
    url.search = "?" + querystring.stringify(url.query);
    /* different libraries handle the multiple data of url onjects
     * differently, we use both the node stdlib and request. let's just
     * serialize for them so our parameters are kept.
     */
    return formatUrl(url);
}

/**
 * Main entry point
 */
exports.fetch = function(url, parameters, cb) {
    exports.discover(url, function(error, alternates) {
	var oembedUrl;
	if (alternates && alternates[MIME_OEMBED_JSON]) {
	    oembedUrl = applyParameters(alternates[MIME_OEMBED_JSON], parameters);
	    exports.fetchJSON(oembedUrl, cb);
	} else if (alternates && alternates[MIME_OEMBED_XML]) {
	    oembedUrl = applyParameters(alternates[MIME_OEMBED_XML], parameters);
	    exports.fetchXML(oembedUrl, cb);
	} else if (exports.EMBEDLY_KEY) {
	    if (!parameters)
		parameters = {};
	    /* Fallback to the Embedly oEmbed API */
	    parameters.key = exports.EMBEDLY_KEY;
	    parameters.url = url;
	    /* Sanitize user-provided parameters */
	    parameters.format = 'json';
	    delete parameters.callback;

	    oembedUrl = applyParameters(exports.EMBEDLY_URL, parameters);
	    exports.fetchJSON(oembedUrl, cb);
	} else
	    cb(error || new Error("No oEmbed links discovered"));
    });
};
<commit_msg>Fix silly little exception when el has no attribs ( https://github.com/astro/node-oembed/issues/2 )<commit_after>var request = require('request');
var htmlparser = require("htmlparser");
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var parseUrl = require('url').parse;
var formatUrl = require('url').format;
var resolveUrl = require('url').resolve;
var querystring = require('querystring');

/**
 * Set this if you want fallback for sites that don't provide oEmbed.
 */
exports.EMBEDLY_KEY = undefined;
exports.EMBEDLY_URL = "http://api.embed.ly/1/oembed";

/**
 * htmlparser is not doing that for us :-(
 */
function expandEntities(s) {
    if (typeof s !== 'string')
	return undefined;
    return s.replace('&', '&').
	replace('<', '<').
	replace('>', '>').
	replace('"', '"').
	replace(''', '\'');
}

function getElementText(el) {
    if (el.children)
	return expandEntities(el.children.map(getElementText).join(""));
    else if (el.type === 'text')
	return expandEntities(el.data);
    else
	return "";
}

/**
 * Wraps request()
 */
function httpGet(url, cb) {
    try {
	return request({ uri: url,
			 jar: false,  /* no cookies */
			 headers: {
			     "User-Agent": exports.USER_AGENT
			 }
		       }, cb);
    } catch (e) {
	/* request() throws at us */
	if (cb)
	    cb(e);
	else
	    throw e;
    }
}

var package = require('../package.json');
exports.USER_AGENT = "node-oembed/" + package.version + " (" + package.homepage + ")";

/**
 * Write stream that collects <link rel='alternate'> @href by @type
 *
 * @param {Function} urlConvert Expands relative to absolute URL
 * @param {Function} cb Final callback
 */
function Discovery(urlConvert, cb) {
    this.urlConvert = urlConvert;
    this.callback = cb;
    var handler = new DiscoveryHandler(this);
    this.parser = new htmlparser.Parser(handler);
    this.alternates = {};
}
util.inherits(Discovery, EventEmitter);

Discovery.prototype.write = function(data) {
    this.parser.parseChunk(data);
    return true;
};

Discovery.prototype.end = function() {
    this.parser.done();
};

Discovery.prototype.addAlternate = function(type, href) {
    if (!this.alternates.hasOwnProperty(type))
	this.alternates[type] = this.urlConvert(href);
};

Discovery.prototype.onDone = function() {
    this.callback(null, this.alternates);
};

/**
 * Methods implement htmlparser handler interface
 *
 * @param {Discovery} disco
 */
function DiscoveryHandler(disco) {
    this.disco = disco;
}

DiscoveryHandler.prototype = {
    reset: function() {
    },
    done: function() {
	this.disco.onDone();
    },
    writeTag: function(el) {
	var attrs = el.attribs;
	var type = attrs && expandEntities(attrs.type);
	var href = attrs && expandEntities(attrs.href);
	if (el.name === 'link' &&
	    attrs && attrs.rel === 'alternate' &&
	    type && href) {
	    this.disco.addAlternate(type, href);
	}
    },
    writeText: function() {
    },
    writeComment: function() {
    },
    writeDirective: function() {
    }
};

exports.discover = function(url, cb) {
    var req;
    try {
	req = httpGet(url);
    } catch (e) {
	return cb(e);
    }

    req.on('response', function(res) {
	if (res.statusCode === 200) {
	    var disco = new Discovery(function(href) {
		return resolveUrl(url, href);
	    }, cb);
	    util.pump(res, disco);
	} else
	    cb(new Error("HTTP status " + res.statusCode));
    });
    req.on('error', function(error) {
	cb(error);
    });
};

function OEmbedXMLParser(cb) {
    this.callback = cb;
    var handler = new htmlparser.DefaultHandler(function(error, dom) {
	var oembedRoot = dom &&
	    dom.filter(function(el) {
		return el.name === 'oembed';
	    })[0];
	if (oembedRoot && oembedRoot.children) {
	    var result = {};
	    oembedRoot.children.forEach(function(child) {
		if (child.name)
		    result[child.name] = getElementText(child);
	    });
	    cb(null, result);
	} else
	    cb(error || new Error("Invalid oEmbed document"));
    });
    this.parser = new htmlparser.Parser(handler);
    this.alternates = {};
}
util.inherits(OEmbedXMLParser, EventEmitter);

OEmbedXMLParser.prototype.write = function(data) {
    this.parser.parseChunk(data);
    return true;
};

OEmbedXMLParser.prototype.end = function() {
    this.parser.done();
};

exports.fetchXML = function(url, cb) {
    var req = httpGet(url);
    req.on('response', function(res) {
	if (res.statusCode === 200) {
	    var parser = new OEmbedXMLParser(cb);
	    util.pump(res, parser);
	} else
	    cb(new Error("HTTP status " + res.statusCode));
    });
    req.on('error', function(error) {
	cb(error);
    });
};

exports.fetchJSON = function(url, cb) {
    httpGet(url, function(error, res, body) {
	if (!error &&
	    res.statusCode === 200 &&
	    body) {
	    try {
		cb(null, JSON.parse(body));
	    } catch (e) {
		cb(e);
	    }
	} else
	    cb(error || new Error("HTTP status " + res.statusCode));
    });
};

var MIME_OEMBED_JSON = exports.MIME_OEMBED_JSON = 'application/json+oembed';
var MIME_OEMBED_XML = exports.MIME_OEMBED_XML = 'text/xml+oembed';
var ALLOWED_PARAMETERS = ["url", "format", "key", "maxwidth", "maxheight"];

function applyParameters(url, parameters) {
    if (!parameters)
	/* Nothing to do, skip */
	return url;

    if (!url.query || typeof url.query === 'string')
	/* Parse querystring */
	url = parseUrl(url, true);

    for(var k in parameters)
	if (ALLOWED_PARAMETERS.indexOf(k) >= 0)
	    url.query[k] = parameters[k];

    /* request assumes .search, not .query */
    url.search = "?" + querystring.stringify(url.query);
    /* different libraries handle the multiple data of url onjects
     * differently, we use both the node stdlib and request. let's just
     * serialize for them so our parameters are kept.
     */
    return formatUrl(url);
}

/**
 * Main entry point
 */
exports.fetch = function(url, parameters, cb) {
    exports.discover(url, function(error, alternates) {
	var oembedUrl;
	if (alternates && alternates[MIME_OEMBED_JSON]) {
	    oembedUrl = applyParameters(alternates[MIME_OEMBED_JSON], parameters);
	    exports.fetchJSON(oembedUrl, cb);
	} else if (alternates && alternates[MIME_OEMBED_XML]) {
	    oembedUrl = applyParameters(alternates[MIME_OEMBED_XML], parameters);
	    exports.fetchXML(oembedUrl, cb);
	} else if (exports.EMBEDLY_KEY) {
	    if (!parameters)
		parameters = {};
	    /* Fallback to the Embedly oEmbed API */
	    parameters.key = exports.EMBEDLY_KEY;
	    parameters.url = url;
	    /* Sanitize user-provided parameters */
	    parameters.format = 'json';
	    delete parameters.callback;

	    oembedUrl = applyParameters(exports.EMBEDLY_URL, parameters);
	    exports.fetchJSON(oembedUrl, cb);
	} else
	    cb(error || new Error("No oEmbed links discovered"));
    });
};
<|endoftext|>


<commit_before>var Mocha = require('mocha');

var mocha = new Mocha();

var passed = [];
var failed = [];

mocha.addFile('autobhan.test');

mocha.timeout(0);

mocha.run(function(){

    console.log(passed.length + ' Tests Passed');
    passed.forEach(function(testName){
        console.log('Passed:', testName);
    });

    console.log("\n"+failed.length + ' Tests Failed');
    failed.forEach(function(testName){
        console.log('Failed:', testName);
    });

}).on('fail', function(test){
    failed.push(test.title);
}).on('pass', function(test){
    passed.push(test.title);
});<commit_msg>Fixed the mocha runner<commit_after>var Mocha = require('mocha');

var mocha = new Mocha();

var passed = [];
var failed = [];

mocha.addFile('autobahn.test');

mocha.timeout(0);

mocha.run(function(){

    console.log(passed.length + ' Tests Passed');
    passed.forEach(function(testName){
        console.log('Passed:', testName);
    });

    console.log("\n"+failed.length + ' Tests Failed');
    failed.forEach(function(testName){
        console.log('Failed:', testName);
    });

}).on('fail', function(test){
    failed.push(test.title);
}).on('pass', function(test){
    passed.push(test.title);
});<|endoftext|>


<commit_before>var ToolComponent = require("substance-ui/tool_component");
var DropdownComponent = require("substance-ui/dropdown_component");

// A simple tool for navigating app states
var NavigateTool = require("./tools/navigate_tool_component");
var Icon = require("substance-ui/font_awesome_icon");

var _ = require("substance/helpers");
var $$ = React.createElement;

var ContentToolbarComponent = React.createClass({

  handleToggleDialog: function(e) {
    e.preventDefault();
    $('.modal').toggleClass('active');
  },

  handleDropdownToggle: function(e) {
    e.preventDefault();
    var $el = $(e.currentTarget).parents('.dropdown');
    if ($el.is('.active')) {
      $el.toggleClass('open');
    }
  },

  render: function() {
    return $$("div", { className: "content-tools-component toolbar small fill-white" },
      $$('div', {className: 'tool-group document clearfix'},
        $$(ToolComponent, { tool: 'undo', title: i18n.t('menu.undo'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-undo"})),
        $$(ToolComponent, { tool: 'redo', title: i18n.t('menu.redo'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-repeat"})),
        $$(ToolComponent, { tool: 'save', title: i18n.t('menu.save'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-save"}))
      ),

      $$('div', {className: 'tool-group actions clearfix'}),

      $$('div', {className: 'tool-group formatting clearfix float-right'},
        $$(ToolComponent, { tool: 'timecode', title: i18n.t('menu.timecode'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-clock-o"})),
        $$(ToolComponent, { tool: 'emphasis', title: i18n.t('menu.emphasis'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-italic"})),
        $$(ToolComponent, { tool: 'strong', title: i18n.t('menu.strong'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-bold"}))
      ),
      $$('div', {className: 'tool-group formatting clearfix float-right'},
        $$(ToolComponent, { tool: 'indentation', title: i18n.t('menu.indentation'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-dedent"})),
        $$(ToolComponent, { tool: 'whitespace', title: i18n.t('menu.whitespace'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-eraser"}))
      ),
      $$('div', {className: 'tool-group formatting clearfix float-right'},
        $$(ToolComponent, { tool: 'comment', title: 'Comment', classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-comment"})),
        $$(ToolComponent, { tool: 'entity_reference', title: 'Tag Entity', classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-bullseye"})),
        $$(ToolComponent, { tool: 'subject_reference', title: 'Tag Subject', classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-tag"}))
      )
    );
  }
});

module.exports = ContentToolbarComponent;

<commit_msg>timecode annotator toolbar<commit_after>var ToolComponent = require("substance-ui/tool_component");
var DropdownComponent = require("substance-ui/dropdown_component");

// A simple tool for navigating app states
var NavigateTool = require("./tools/navigate_tool_component");
var Icon = require("substance-ui/font_awesome_icon");

var _ = require("substance/helpers");
var $$ = React.createElement;

var ContentToolbarComponent = React.createClass({

  handleToggleDialog: function(e) {
    e.preventDefault();
    $('.modal').toggleClass('active');
  },

  handleDropdownToggle: function(e) {
    e.preventDefault();
    var $el = $(e.currentTarget).parents('.dropdown');
    if ($el.is('.active')) {
      $el.toggleClass('open');
    }
  },

  render: function() {
    return $$("div", { className: "content-tools-component toolbar small fill-white" },
      $$('div', {className: 'tool-group document clearfix'},
        $$(ToolComponent, { tool: 'undo', title: i18n.t('menu.undo'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-undo"})),
        $$(ToolComponent, { tool: 'redo', title: i18n.t('menu.redo'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-repeat"})),
        $$(ToolComponent, { tool: 'save', title: i18n.t('menu.save'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-save"}))
      ),

      $$('div', {className: 'tool-group actions clearfix'}),

      $$('div', {className: 'tool-group formatting clearfix float-right'},
        $$(ToolComponent, { tool: 'timecode', title: i18n.t('menu.timecode'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-clock-o"})),
        $$(ToolComponent, { tool: 'emphasis', title: i18n.t('menu.emphasis'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-italic"})),
        $$(ToolComponent, { tool: 'strong', title: i18n.t('menu.strong'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-bold"}))
      ),
      $$('div', {className: 'tool-group formatting clearfix float-right'},
        $$(ToolComponent, { tool: 'indentation', title: i18n.t('menu.indentation'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-dedent"})),
        $$(ToolComponent, { tool: 'whitespace', title: i18n.t('menu.whitespace'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-eraser"})),
        $$(ToolComponent, { tool: 'timecode_annotator', title: i18n.t('menu.timecode_annotator'), classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-history"}))
      ),
      $$('div', {className: 'tool-group formatting clearfix float-right'},
        $$(ToolComponent, { tool: 'comment', title: 'Comment', classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-comment"})),
        $$(ToolComponent, { tool: 'entity_reference', title: 'Tag Entity', classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-bullseye"})),
        $$(ToolComponent, { tool: 'subject_reference', title: 'Tag Subject', classNames: ['button', 'tool']}, $$(Icon, {icon: "fa-tag"}))
      )
    );
  }
});

module.exports = ContentToolbarComponent;

<|endoftext|>


<commit_before>const UTTT = require('ultimate-ttt');
const ip = require('ip');
const blessed = require('blessed');

const funcs = require('./funcs');
const State = require('./state');

const serverInfo = {
  players: [],
  games: [],
  nextGame: 0,
  ui: {},
};

/**
 * Start an online game server - supports multiple games at once
 * It's also the module export of this file
 * @param options
 */
function onlineGame(options, screen, header) {
  const host = 'localhost';
  const port = options.port || 3141;

  const app = require('http').createServer(handler);
  const io = require('socket.io')(app);
  const fs = require('fs');

  app.listen(port);

  header.insertBottom('{yellow-fg}Local address:{/yellow-fg}   http://' + host + ':' + port);
  header.insertBottom('{yellow-fg}Network address:{/yellow-fg} http://' + ip.address() + ':' + port);

  serverInfo.ui.screen = screen;

  serverInfo.ui.onlineUsers = blessed.box({
    parent: screen,
    top: 4,
    left: 0,
    width: 20,
    height: '100%-4',
    tags: true,
    content: '{bold}Online users{/bold}',
    border: 'line',
    style: {
      border: {
        fg: 'blue',
      }
    }
  });

  serverInfo.ui.gameArea = blessed.box({
    parent: screen,
    top: 4,
    left: 21,
    width: '100%-22',
    height: '100%-14',
    content: '{bold}Games{/bold}',
    tags: true,
    border: 'line',
    style: {
      border: {
        fg: 'blue',
      }
    }
  });

  serverInfo.ui.logger = blessed.log({
    parent: screen,
    bottom: 0,
    left: 21,
    width: '100%-22',
    height: 10,
    border: 'line',
    tags: true,
    keys: true,
    vi: true,
    mouse: true,
    scrollback: 100,
    scrollbar: {
      ch: ' ',
      track: {
        bg: 'yellow'
      },
      style: {
        inverse: true
      }
    },
    style: {
      border: {
        fg: 'blue',
      }
    }
  });

  log('Server started', true);

  function handler (req, res) {
    fs.readFile(__dirname + '/../public/index.html',
      function (err, data) {
        if (err) {
          res.writeHead(500);
          return res.end('Error loading index.html');
        }

        res.writeHead(200);
        res.end(data);
      });
  }

  io.set('authorization', function (data, accept) {
    const token = data._query.token;
    if (!token) {
      return accept('Missing token', false);
    }
    data.testToken = token;
    accept(null, true);
  });

  io.on('connection', function (socket) {

    const playerIndex = addPlayer(socket.handshake.query.token);

    if (!serverInfo.games[serverInfo.nextGame]) {
      serverInfo.games[serverInfo.nextGame] = {
        players: []
      };
    } else if (serverInfo.games[serverInfo.nextGame].players.length >= 2) {
      serverInfo.nextGame++;
      serverInfo.games[serverInfo.nextGame] = {
        players: []
      };
    }

    const session = serverInfo.games[serverInfo.nextGame];
    const player = session.players.length;
    session.players[player] = {
      playerIndex: playerIndex,
      socket: socket
    };

    if (session.players.length >= 2) {
      startSession(session, options);
      serverInfo.nextGame++;
    }

    socket.emit('game', {
      action: 'waiting'
    });
  });

  screen.render();
}

function log(text, skipRender) {
  const time = (new Date()).toTimeString().substr(0,5);
  serverInfo.ui.logger.log('{blue-fg}[' + time + ']{/blue-fg} ' + text);
  if (!skipRender) {
    serverInfo.ui.screen.render();
  }
}

function addPlayer(player) {
  let index = -1;
  if (serverInfo.players.indexOf(player) < 0) {
    index = serverInfo.players.push(player) - 1;
  }
  log('Connected "' + player + '"', true);
  renderOnlinePlayers();
  return index;
}

function removePlayer(player) {
  const index = serverInfo.players.indexOf(player);
  if (index > -1) {
    serverInfo.players.splice(index, 1);
  }
  log('Disconnected "' + player + '"', true);
  renderOnlinePlayers();
}

function renderOnlinePlayers() {
  serverInfo.ui.onlineUsers.content = '{bold}Online players{/bold}';
  serverInfo.ui.onlineUsers.render();
  serverInfo.players.forEach(function(player) {
    serverInfo.ui.onlineUsers.insertBottom(' ' + player);
  });

  serverInfo.ui.screen.render();
}

/**
 * Take a game holder with two connected players and start
 * playing
 * @param session Game session with two active players ready
 */
function startSession(session, settings){
  const options = settings || {};

  log('Starting games between "' +
    serverInfo.players[session.players[0].playerIndex] +
    '" and "' +
    serverInfo.players[session.players[1].playerIndex] +
    '"'
  );

  const server = {
    timeout: options.timeout || 100,
    maxGames: options.games || 1000,
    state: new State(),
    session: session,
    currentPlayer: 0,
    firstPlayer: 0,
    game: new UTTT(),
    gameStart: null,
    ui: getGameBox(),
    progressBar: null,
  };

  server.ui.append(blessed.text({
    top: 0,
    left: 0,
    style: {
      bg: 'black'
    },
    tags: true,
    content: '{red-fg}"' +
      serverInfo.players[session.players[0].playerIndex] +
    '"{/red-fg} vs {red-fg}"' +
    serverInfo.players[session.players[1].playerIndex] +
    '"{/red-fg}'
  }));

  server.progressBar = getProgressBar();
  server.ui.append(server.progressBar);
  server.ui.render();

  serverInfo.ui.gameArea.render();
  serverInfo.ui.screen.render();

  session.players[0].socket.on('disconnect', function() {
    removePlayer(session.players[0].handshake.query.token);
    handleGameEnd(1, true);
  });
  session.players[1].socket.on('disconnect', function() {
    removePlayer(session.players[1].socket.handshake.query.token);
    handleGameEnd(0, true);
  });

  // Receive input from a player
  session.players[0].socket.on('game', handlePlayerMove(0));
  session.players[1].socket.on('game', handlePlayerMove(1));

  // Start game session
  playGame();

  function handleGameEnd(winner, playerDisconnected) {
    const hrend = process.hrtime(server.gameStart);
    server.state.times.push(funcs.convertExecTime(hrend[1]));
    if (winner > -1) {
      server.state.wins[winner]++;
      server.firstPlayer = winner;
    } else {
      server.firstPlayer = 1 - server.firstPlayer;
      server.state.ties++;
    }

    const progress = Math.floor(server.state.games * 100 / server.maxGames);
    server.progressBar.setProgress(progress);
    serverInfo.ui.screen.render();

    if (!playerDisconnected && server.state.games < server.maxGames) {
      playGame(server.session, server.firstPlayer);
    } else {
      sessionEnd();
    }
  }

  /**
   * Handle the end of a session between two players
   */
  function sessionEnd() {
    const players = [
      serverInfo.players[session.players[0].playerIndex],
      serverInfo.players[session.players[1].playerIndex],
    ];
    log('Finished games between "' +
      players[0] +
      '" and "' +
      players[1]  +
      '"'
    );

    server.progressBar.destroy();
    serverInfo.ui.screen.render();

    const stats = funcs.getStats(server.state);
    let winner = '-';
    if (stats.winner > -1){
      winner = players[stats.winner];
    }

    server.ui.append(blessed.text({
      top: 1,
      left: 0,
      style: {
        bg: 'black'
      },
      tags: true,
      content: '{blue-fg}Winner:{/blue-fg} ' + winner +
        '. {blue-fg}Wins:{/blue-fg} {yellow-fg}Player "' + players[0] + '"{/yellow-fg} ' + server.state.wins[0] + ' (' + stats.winPercentages[0] + '%) - ' +
        '{yellow-fg}Player "' + players[1] + '"{/yellow-fg} ' + server.state.wins[1] + ' (' + stats.winPercentages[1] + '%) - ' +
        '{blue-fg}Ties{/blue-fg} ' + server.state.ties + ' (' + stats.tiePercentage + '%)'
    }));

    server.ui.append(blessed.text({
      top: 2,
      left: 0,
      style: {
        bg: 'black'
      },
      tags: true,
      content: '{yellow-fg}Total time:{/yellow-fg} ' + stats.total + 'ms. ' +
              '{yellow-fg}Avg:{/yellow-fg} ' + stats.avg + 'ms. ' +
              '{yellow-fg}Max:{/yellow-fg} ' + stats.max + 'ms. ' +
              '{yellow-fg}Min:{/yellow-fg} ' + stats.min + 'ms'
    }));

    // All done
    //funcs.printState(server.state);
  }

  /**
   * Play an individual game between two players
   */
  function playGame() {
    server.gameStart = process.hrtime();
    server.state.games++;
    server.game = new UTTT();
    server.currentPlayer = server.firstPlayer;
    server.game.init();

    sendAction(server.session, 'init', 0);
    sendAction(server.session, 'init', 1);

    sendAction(server.session, 'move', server.firstPlayer);
  }

  /**
   * Handles a specific player move - this returns a function
   * configured for the specified player, it's intended to be used
   * as the callback for the player socket.
   * @param player Player number (0-1)
   * @returns {Function}
   */
  function handlePlayerMove(player) {
    return function(data) {
      if (server.currentPlayer !== player) {
        log('Game ' + server.state.games + ': Player ' + player + ' played out of time (it was ' + server.currentPlayer + ' turn)');
        handleGameEnd(server.currentPlayer);
        return;
      }
      if (data === 'fail') {
        handleGameEnd(1 - server.currentPlayer);
        return;
      }
      try {
        const coords = parseMove(data);
        server.game.move(coords.board, server.currentPlayer + 1, coords.move);
        if (server.game.isFinished()) {
          handleGameEnd(server.game.winner - 1);
          return;
        }
        server.currentPlayer = 1 - server.currentPlayer;
        sendAction(session, 'opponent ' + writeMove(coords), server.currentPlayer);
      } catch(e) {
        log('Game ' + server.state.games + ': Player ' + server.currentPlayer + ' errored: ' + e.message);
        handleGameEnd(1 - server.currentPlayer);
      }
    };
  }
}

function getGameBox() {
  const height = 3;
  return blessed.box({
    parent: serverInfo.ui.gameArea,
    top: 2 + (serverInfo.games.length - 1) * height,
    left: 0,
    width: '100%-2',
    height: height,
    tags: true,
    style: {
      bg: 'black'
    }
  });
}

function getProgressBar() {
  return blessed.progressbar({
    top: 2,
    left: 0,
    orientation: 'horizontal',
    keys: false,
    mouse: false,
    filled: 0,
    style: {
      bg: 'black',
      bar: {
        bg: 'blue'
      },
      border: {
        bg: 'black'
      }
    },
    width: '100%',
    height: 1,
  });
}

/**
 * Send an action to a player
 * @param session Session object
 * @param action Action to send
 * @param player Player number (0-1)
 */
function sendAction(session, action, player) {
  if (session.players[player]) {
    session.players[player].socket.emit('game', {
      action: action
    });
  }
}

/**
 * Receive a move from a player and convert it to an object
 * of board and move
 * @param data String received from the client
 * @returns {{board: Array, move: Array}}
 */
function parseMove(data) {
  const parts = data.trim().split(';');
  return {
    board: parts[0].split(',').map(function(coord) { return parseInt(coord, 10); }),
    move: parts[1].split(',').map(function(coord) { return parseInt(coord, 10); })
  };
}

/**
 * Encode a move received from a player to be sent to the opponent
 * @param coords {{board: Array, move: Array}}
 * @returns {string}
 */
function writeMove(coords) {
  return coords.board[0] + ',' + coords.board[1] + ';' +
      coords.move[0] + ',' + coords.move[1];
}

module.exports = onlineGame;<commit_msg>fix disconnect user removal<commit_after>const UTTT = require('ultimate-ttt');
const ip = require('ip');
const blessed = require('blessed');

const funcs = require('./funcs');
const State = require('./state');

const serverInfo = {
  players: [],
  games: [],
  nextGame: 0,
  ui: {},
};

/**
 * Start an online game server - supports multiple games at once
 * It's also the module export of this file
 * @param options
 */
function onlineGame(options, screen, header) {
  const host = 'localhost';
  const port = options.port || 3141;

  const app = require('http').createServer(handler);
  const io = require('socket.io')(app);
  const fs = require('fs');

  app.listen(port);

  header.insertBottom('{yellow-fg}Local address:{/yellow-fg}   http://' + host + ':' + port);
  header.insertBottom('{yellow-fg}Network address:{/yellow-fg} http://' + ip.address() + ':' + port);

  serverInfo.ui.screen = screen;

  serverInfo.ui.onlineUsers = blessed.box({
    parent: screen,
    top: 4,
    left: 0,
    width: 20,
    height: '100%-4',
    tags: true,
    content: '{bold}Online users{/bold}',
    border: 'line',
    style: {
      border: {
        fg: 'blue',
      }
    }
  });

  serverInfo.ui.gameArea = blessed.box({
    parent: screen,
    top: 4,
    left: 21,
    width: '100%-22',
    height: '100%-14',
    content: '{bold}Games{/bold}',
    tags: true,
    border: 'line',
    style: {
      border: {
        fg: 'blue',
      }
    }
  });

  serverInfo.ui.logger = blessed.log({
    parent: screen,
    bottom: 0,
    left: 21,
    width: '100%-22',
    height: 10,
    border: 'line',
    tags: true,
    keys: true,
    vi: true,
    mouse: true,
    scrollback: 100,
    scrollbar: {
      ch: ' ',
      track: {
        bg: 'yellow'
      },
      style: {
        inverse: true
      }
    },
    style: {
      border: {
        fg: 'blue',
      }
    }
  });

  log('Server started', true);

  function handler (req, res) {
    fs.readFile(__dirname + '/../public/index.html',
      function (err, data) {
        if (err) {
          res.writeHead(500);
          return res.end('Error loading index.html');
        }

        res.writeHead(200);
        res.end(data);
      });
  }

  io.set('authorization', function (data, accept) {
    const token = data._query.token;
    if (!token) {
      return accept('Missing token', false);
    }
    data.testToken = token;
    accept(null, true);
  });

  io.on('connection', function (socket) {

    const playerIndex = addPlayer(socket.handshake.query.token);

    if (!serverInfo.games[serverInfo.nextGame]) {
      serverInfo.games[serverInfo.nextGame] = {
        players: []
      };
    } else if (serverInfo.games[serverInfo.nextGame].players.length >= 2) {
      serverInfo.nextGame++;
      serverInfo.games[serverInfo.nextGame] = {
        players: []
      };
    }

    const session = serverInfo.games[serverInfo.nextGame];
    const player = session.players.length;
    session.players[player] = {
      playerIndex: playerIndex,
      socket: socket
    };

    if (session.players.length >= 2) {
      startSession(session, options);
      serverInfo.nextGame++;
    }

    socket.emit('game', {
      action: 'waiting'
    });
  });

  screen.render();
}

function log(text, skipRender) {
  const time = (new Date()).toTimeString().substr(0,5);
  serverInfo.ui.logger.log('{blue-fg}[' + time + ']{/blue-fg} ' + text);
  if (!skipRender) {
    serverInfo.ui.screen.render();
  }
}

function addPlayer(player) {
  let index = -1;
  if (serverInfo.players.indexOf(player) < 0) {
    index = serverInfo.players.push(player) - 1;
  }
  log('Connected "' + player + '"', true);
  renderOnlinePlayers();
  return index;
}

function removePlayer(player) {
  const index = serverInfo.players.indexOf(player);
  if (index > -1) {
    serverInfo.players.splice(index, 1);
  }
  log('Disconnected "' + player + '"', true);
  renderOnlinePlayers();
}

function renderOnlinePlayers() {
  serverInfo.ui.onlineUsers.content = '{bold}Online players{/bold}';
  serverInfo.ui.onlineUsers.render();
  serverInfo.players.forEach(function(player) {
    serverInfo.ui.onlineUsers.insertBottom(' ' + player);
  });

  serverInfo.ui.screen.render();
}

/**
 * Take a game holder with two connected players and start
 * playing
 * @param session Game session with two active players ready
 */
function startSession(session, settings){
  const options = settings || {};

  log('Starting games between "' +
    serverInfo.players[session.players[0].playerIndex] +
    '" and "' +
    serverInfo.players[session.players[1].playerIndex] +
    '"'
  );

  const server = {
    timeout: options.timeout || 100,
    maxGames: options.games || 1000,
    state: new State(),
    session: session,
    currentPlayer: 0,
    firstPlayer: 0,
    game: new UTTT(),
    gameStart: null,
    ui: getGameBox(),
    progressBar: null,
  };

  server.ui.append(blessed.text({
    top: 0,
    left: 0,
    style: {
      bg: 'black'
    },
    tags: true,
    content: '{red-fg}"' +
      serverInfo.players[session.players[0].playerIndex] +
    '"{/red-fg} vs {red-fg}"' +
    serverInfo.players[session.players[1].playerIndex] +
    '"{/red-fg}'
  }));

  server.progressBar = getProgressBar();
  server.ui.append(server.progressBar);
  server.ui.render();

  serverInfo.ui.gameArea.render();
  serverInfo.ui.screen.render();

  session.players[0].socket.on('disconnect', function() {
    removePlayer(serverInfo.players[session.players[0].playerIndex]);
    handleGameEnd(1, true);
  });
  session.players[1].socket.on('disconnect', function() {
    removePlayer(serverInfo.players[session.players[1].playerIndex]);
    handleGameEnd(0, true);
  });

  // Receive input from a player
  session.players[0].socket.on('game', handlePlayerMove(0));
  session.players[1].socket.on('game', handlePlayerMove(1));

  // Start game session
  playGame();

  function handleGameEnd(winner, playerDisconnected) {
    const hrend = process.hrtime(server.gameStart);
    server.state.times.push(funcs.convertExecTime(hrend[1]));
    if (winner > -1) {
      server.state.wins[winner]++;
      server.firstPlayer = winner;
    } else {
      server.firstPlayer = 1 - server.firstPlayer;
      server.state.ties++;
    }

    const progress = Math.floor(server.state.games * 100 / server.maxGames);
    server.progressBar.setProgress(progress);
    serverInfo.ui.screen.render();

    if (!playerDisconnected && server.state.games < server.maxGames) {
      playGame(server.session, server.firstPlayer);
    } else {
      sessionEnd();
    }
  }

  /**
   * Handle the end of a session between two players
   */
  function sessionEnd() {
    const players = [
      serverInfo.players[session.players[0].playerIndex],
      serverInfo.players[session.players[1].playerIndex],
    ];
    log('Finished games between "' +
      players[0] +
      '" and "' +
      players[1]  +
      '"'
    );

    server.progressBar.destroy();
    serverInfo.ui.screen.render();

    const stats = funcs.getStats(server.state);
    let winner = '-';
    if (stats.winner > -1){
      winner = players[stats.winner];
    }

    server.ui.append(blessed.text({
      top: 1,
      left: 0,
      style: {
        bg: 'black'
      },
      tags: true,
      content: '{blue-fg}Winner:{/blue-fg} ' + winner +
        '. {blue-fg}Wins:{/blue-fg} {yellow-fg}Player "' + players[0] + '"{/yellow-fg} ' + server.state.wins[0] + ' (' + stats.winPercentages[0] + '%) - ' +
        '{yellow-fg}Player "' + players[1] + '"{/yellow-fg} ' + server.state.wins[1] + ' (' + stats.winPercentages[1] + '%) - ' +
        '{blue-fg}Ties{/blue-fg} ' + server.state.ties + ' (' + stats.tiePercentage + '%)'
    }));

    server.ui.append(blessed.text({
      top: 2,
      left: 0,
      style: {
        bg: 'black'
      },
      tags: true,
      content: '{yellow-fg}Total time:{/yellow-fg} ' + stats.total + 'ms. ' +
              '{yellow-fg}Avg:{/yellow-fg} ' + stats.avg + 'ms. ' +
              '{yellow-fg}Max:{/yellow-fg} ' + stats.max + 'ms. ' +
              '{yellow-fg}Min:{/yellow-fg} ' + stats.min + 'ms'
    }));

    // All done
    //funcs.printState(server.state);
  }

  /**
   * Play an individual game between two players
   */
  function playGame() {
    server.gameStart = process.hrtime();
    server.state.games++;
    server.game = new UTTT();
    server.currentPlayer = server.firstPlayer;
    server.game.init();

    sendAction(server.session, 'init', 0);
    sendAction(server.session, 'init', 1);

    sendAction(server.session, 'move', server.firstPlayer);
  }

  /**
   * Handles a specific player move - this returns a function
   * configured for the specified player, it's intended to be used
   * as the callback for the player socket.
   * @param player Player number (0-1)
   * @returns {Function}
   */
  function handlePlayerMove(player) {
    return function(data) {
      if (server.currentPlayer !== player) {
        log('Game ' + server.state.games + ': Player ' + player + ' played out of time (it was ' + server.currentPlayer + ' turn)');
        handleGameEnd(server.currentPlayer);
        return;
      }
      if (data === 'fail') {
        handleGameEnd(1 - server.currentPlayer);
        return;
      }
      try {
        const coords = parseMove(data);
        server.game.move(coords.board, server.currentPlayer + 1, coords.move);
        if (server.game.isFinished()) {
          handleGameEnd(server.game.winner - 1);
          return;
        }
        server.currentPlayer = 1 - server.currentPlayer;
        sendAction(session, 'opponent ' + writeMove(coords), server.currentPlayer);
      } catch(e) {
        log('Game ' + server.state.games + ': Player ' + server.currentPlayer + ' errored: ' + e.message);
        handleGameEnd(1 - server.currentPlayer);
      }
    };
  }
}

function getGameBox() {
  const height = 3;
  return blessed.box({
    parent: serverInfo.ui.gameArea,
    top: 2 + (serverInfo.games.length - 1) * height,
    left: 0,
    width: '100%-2',
    height: height,
    tags: true,
    style: {
      bg: 'black'
    }
  });
}

function getProgressBar() {
  return blessed.progressbar({
    top: 2,
    left: 0,
    orientation: 'horizontal',
    keys: false,
    mouse: false,
    filled: 0,
    style: {
      bg: 'black',
      bar: {
        bg: 'blue'
      },
      border: {
        bg: 'black'
      }
    },
    width: '100%',
    height: 1,
  });
}

/**
 * Send an action to a player
 * @param session Session object
 * @param action Action to send
 * @param player Player number (0-1)
 */
function sendAction(session, action, player) {
  if (session.players[player]) {
    session.players[player].socket.emit('game', {
      action: action
    });
  }
}

/**
 * Receive a move from a player and convert it to an object
 * of board and move
 * @param data String received from the client
 * @returns {{board: Array, move: Array}}
 */
function parseMove(data) {
  const parts = data.trim().split(';');
  return {
    board: parts[0].split(',').map(function(coord) { return parseInt(coord, 10); }),
    move: parts[1].split(',').map(function(coord) { return parseInt(coord, 10); })
  };
}

/**
 * Encode a move received from a player to be sent to the opponent
 * @param coords {{board: Array, move: Array}}
 * @returns {string}
 */
function writeMove(coords) {
  return coords.board[0] + ',' + coords.board[1] + ';' +
      coords.move[0] + ',' + coords.move[1];
}

module.exports = onlineGame;<|endoftext|>


<commit_before>var cp = require('child_process')
var os = require('os')
var path = require('path')

var CHROME = process.env.CHROME || '/Applications/Google\\ Chrome\\ Canary.app/Contents/MacOS/Google\\ Chrome\\ Canary'

switch (os.platform()) {
  case 'win32' :
    if (process.arch === 'x64') {
      CHROME = process.env.CHROME || '\"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe\"'
    } else {
      CHROME = process.env.CHROME || '\"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe\"'
    }
    break
  case 'darwin' :
    CHROME = process.env.CHROME || '/Applications/Google\\ Chrome\\ Canary.app/Contents/MacOS/Google\\ Chrome\\ Canary'
    break
  default :
    break
}

var app = path.join(__dirname, '..', 'test/chrome-app')
var command = CHROME + ' --load-and-launch-app=' + app
var env = { cwd: path.join(__dirname, '..') }

cp.exec(command, env, function () {})
<commit_msg>linux chrome support<commit_after>var cp = require('child_process')
var os = require('os')
var path = require('path')

var CHROME = process.env.CHROME || '/Applications/Google\\ Chrome\\ Canary.app/Contents/MacOS/Google\\ Chrome\\ Canary'

switch (os.platform()) {
  case 'win32' :
    if (process.arch === 'x64') {
      CHROME = process.env.CHROME || '\"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe\"'
    } else {
      CHROME = process.env.CHROME || '\"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe\"'
    }
    break
  case 'darwin' :
    CHROME = process.env.CHROME || '/Applications/Google\\ Chrome\\ Canary.app/Contents/MacOS/Google\\ Chrome\\ Canary'
    break
  case 'linux' :
    CHROME = process.env.CHROME || '/opt/google/chrome/chrome'
    break
  default :
    break
}

var app = path.join(__dirname, '..', 'test/chrome-app')
var command = CHROME + ' --load-and-launch-app=' + app
var env = { cwd: path.join(__dirname, '..') }

cp.exec(command, env, function () {})
<|endoftext|>