<commit_before>
var crypto      = require('crypto')
var Step        = require('step')
var fs          = require('fs')
var _           = require('underscore')
var PSQL        = require(global.settings.app_root + '/app/models/psql')
var spawn       = require('child_process').spawn

// Keeps track of what's waiting baking for export
var bakingExports = {};

function ogr(id) {
  this.id = id;
}

ogr.prototype = {

  id: "ogr",

  is_file: true,

  getQuery: function(sql, options) {
    return null; // dont execute the query
  },

  transform: function(result, options, callback) {
    throw "should not be called for file formats"
  },

  getContentType: function(){ return this._contentType; },

  getFileExtension: function(){ return this._fileExtension; },

  getKey: function(options) {
    return [this.id,
        options.dbopts.dbname,
        options.dbopts.user,
        options.gn,
        this.generateMD5(options.filename),
        this.generateMD5(options.sql)].concat(options.skipfields).join(':');
  },

  generateMD5: function (data){
    var hash = crypto.createHash('md5');
    hash.update(data);
    return hash.digest('hex');
  }

};

// Internal function usable by all OGR-driven outputs
ogr.prototype.toOGR = function(options, out_format, out_filename, callback) {

  var gcol = options.gn;
  var sql = options.sql;
  var skipfields = options.skipfields;
  var out_layername = options.filename;

  var dbopts = options.dbopts;

  var ogr2ogr = 'ogr2ogr'; // FIXME: make configurable
  var dbhost = dbopts.host;
  var dbport = dbopts.port;
  var dbuser = dbopts.user;
  var dbpass = dbopts.pass;
  var dbname = dbopts.dbname;

  var that = this;

  var columns = [];
  var geocol;
  var pg;

  // Drop ending semicolon (ogr doens't like it)
  sql = sql.replace(/;\s*$/, '');

  Step (

    function fetchColumns() {
      var colsql = 'SELECT * FROM (' + sql + ') as _cartodbsqlapi LIMIT 0';
      pg = new PSQL(dbopts);
      pg.query(colsql, this);
    },
    function findSRS(err, result) {
      if (err) throw err;

      //if ( ! result.rows.length ) throw new Error("Query returns no rows");

      var needSRS = that._needSRS;

      // Skip system columns, find geom column
      for (var i=0; i<result.fields.length; ++i) {
        var field = result.fields[i];
        var k = field.name;
        if ( skipfields.indexOf(k) != -1 ) continue;
        if ( out_format != 'CSV' && k == "the_geom_webmercator" ) continue; // TODO: drop ?
        if ( out_format == 'CSV' ) columns.push(pg.quoteIdentifier(k)+'::text');
        else columns.push(pg.quoteIdentifier(k));

        if ( needSRS ) {
          if ( ! geocol && pg.typeName(field.dataTypeID) == 'geometry' ) {
            geocol = k
          }
        }
      }
      //console.log(columns.join(','));

      if ( ! needSRS || ! geocol ) return null;

      var next = this;

      var qgeocol = pg.quoteIdentifier(geocol);
      var sridsql = 'SELECT ST_Srid(' + qgeocol + ') as srid, GeometryType(' +
                   qgeocol + ') as type FROM (' + sql + ') as _cartodbsqlapi WHERE ' +
                   qgeocol + ' is not null limit 1';

      pg.query(sridsql, function(err, result) {
        if ( err ) { next(err); return; }
        if ( result.rows.length ) {
          var srid = result.rows[0].srid;
          var type = result.rows[0].type;
          next(null, srid, type);
        }
      });

    },
    function spawnDumper(err, srid, type) {
      if (err) throw err;

      var next = this;

      var ogrsql = 'SELECT ' + columns.join(',')
          + ' FROM (' + sql + ') as _cartodbsqlapi';

      var ogrargs = [
        '-f', out_format,
        '-lco', 'ENCODING=UTF-8',
        '-lco', 'LINEFORMAT=CRLF',
        out_filename,
        "PG:host=" + dbhost
         + " user=" + dbuser
         + " dbname=" + dbname
         + " password=" + dbpass
         + " tables=fake" // trick to skip query to geometry_columns (private)
                          // in turn breaks knowing SRID with gdal-0.10.1:
                          // http://github.com/CartoDB/CartoDB-SQL-API/issues/110
         + "",
        '-sql', ogrsql
      ];

      if ( srid ) {
        ogrargs.push('-a_srs', 'EPSG:'+srid);
      }

      if ( type ) {
        ogrargs.push('-nlt', type);
      }

      ogrargs.push('-nln', out_layername);

      var child = spawn(ogr2ogr, ogrargs);

/*
console.log('ogr2ogr ' + _.map(ogrargs, function(x) { return "'" + x + "'"; }).join(' '));
*/

      var stdout = '';
      child.stdout.on('data', function(data) {
        stdout += data;
        //console.log('stdout: ' + data);
      });

      var stderr;
      var logErrPat = new RegExp(/^ERROR/);
      child.stderr.on('data', function(data) {
        data = data.toString(); // know of a faster way ?
        // Store only the first ERROR line
        if ( ! stderr && data.match(logErrPat) ) stderr = data;
        console.log('ogr2ogr stderr: ' + data);
      });

      child.on('exit', function(code) {
        if ( code ) {
          console.log("ogr2ogr exited with code " + code);
          var emsg = stderr ? stderr.split('\n')[0] : ( "unknown ogr2ogr error (code " + code + ")" );
          // TODO: add more info about this error ?
          //if ( RegExp(/attempt to write non-.*geometry.*to.*type shapefile/i).exec(emsg) )
          next(new Error(emsg));
        } else {
          next(null);
        }
      });
    },
    function finish(err) {
      callback(err, out_filename);
    }
  );
};

ogr.prototype.toOGR_SingleFile = function(options, fmt, callback) {

  var dbname = options.dbopts.dbname;
  var user_id = options.dbopts.user;
  var gcol = options.gcol;
  var sql = options.sql;
  var skipfields = options.skipfields;
  var ext = this._fileExtension;
  var layername = options.filename;

  var tmpdir = global.settings.tmpDir || '/tmp';
  var reqKey = [ fmt, dbname, user_id, gcol, this.generateMD5(layername), this.generateMD5(sql) ].concat(skipfields).join(':');
  var outdirpath = tmpdir + '/sqlapi-' + process.pid + '-' + reqKey;
  var dumpfile = outdirpath + ':cartodb-query.' + ext;

  // TODO: following tests:
  //  - fetch query with no "the_geom" column
  this.toOGR(options, fmt, dumpfile, callback);
};

ogr.prototype.sendResponse = function(opts, callback) {
  var next = callback;
  var reqKey = this.getKey(opts);
  var qElem = new ExportRequest(opts.sink, callback);
  var baking = bakingExports[reqKey];
  if ( baking ) {
    baking.req.push( qElem );
  } else {
    baking = bakingExports[reqKey] = { req: [ qElem ] };
    this.generate(opts, function(err, dumpfile) {
      Step (
        function sendResults() {
          var nextPipe = function(finish) {
            var r = baking.req.shift();
            if ( ! r ) { finish(null); return; }
            r.sendFile(err, dumpfile, function() {
              nextPipe(finish);
            });
          }

          if ( ! err ) nextPipe(this);
          else {
            _.each(baking.req, function(r) {
              r.cb(err);
            });
            return true;
          }
        },
        function cleanup(err) {
          delete bakingExports[reqKey];

          // unlink dump file (sync to avoid race condition)
          console.log("removing", dumpfile);
          try { fs.unlinkSync(dumpfile); }
          catch (e) {
            if ( e.code != 'ENOENT' ) {
              console.log("Could not unlink dumpfile " + dumpfile + ": " + e);
            }
          }
        }
      );
    })
  }
  return;
};

// TODO: put in an ExportRequest.js ----- {

function ExportRequest(ostream, callback) {
  this.cb = callback;
  this.ostream = ostream;
  this.istream = null;
  this.canceled = false;

  var that = this;

  this.ostream.on('close', function() {
    //console.log("Request close event, qElem.stream is " + qElem.stream);
    that.canceled = true;
    if ( that.istream ) {
      that.istream.destroy();
    }
  });
}

ExportRequest.prototype.sendFile = function (err, filename, callback) {
  var that = this;
  if ( ! this.canceled ) {
    //console.log("Creating readable stream out of dumpfile");
    this.istream = fs.createReadStream(filename)
    .on('open', function(fd) {
      that.istream.pipe(that.ostream);
      callback();
    })
    .on('error', function(e) {
      console.log("Can't send response: " + e);
      that.ostream.end();
      callback();
    });
  } else {
    //console.log("Response was canceled, not streaming the file");
    callback();
  }
  this.cb();
}

//------ }

module.exports = ogr;
<commit_msg>Use db_port in ogr2ogr<commit_after>
var crypto      = require('crypto')
var Step        = require('step')
var fs          = require('fs')
var _           = require('underscore')
var PSQL        = require(global.settings.app_root + '/app/models/psql')
var spawn       = require('child_process').spawn

// Keeps track of what's waiting baking for export
var bakingExports = {};

function ogr(id) {
  this.id = id;
}

ogr.prototype = {

  id: "ogr",

  is_file: true,

  getQuery: function(sql, options) {
    return null; // dont execute the query
  },

  transform: function(result, options, callback) {
    throw "should not be called for file formats"
  },

  getContentType: function(){ return this._contentType; },

  getFileExtension: function(){ return this._fileExtension; },

  getKey: function(options) {
    return [this.id,
        options.dbopts.dbname,
        options.dbopts.user,
        options.gn,
        this.generateMD5(options.filename),
        this.generateMD5(options.sql)].concat(options.skipfields).join(':');
  },

  generateMD5: function (data){
    var hash = crypto.createHash('md5');
    hash.update(data);
    return hash.digest('hex');
  }

};

// Internal function usable by all OGR-driven outputs
ogr.prototype.toOGR = function(options, out_format, out_filename, callback) {

  var gcol = options.gn;
  var sql = options.sql;
  var skipfields = options.skipfields;
  var out_layername = options.filename;

  var dbopts = options.dbopts;

  var ogr2ogr = 'ogr2ogr'; // FIXME: make configurable
  var dbhost = dbopts.host;
  var dbport = dbopts.port;
  var dbuser = dbopts.user;
  var dbpass = dbopts.pass;
  var dbname = dbopts.dbname;

  var that = this;

  var columns = [];
  var geocol;
  var pg;

  // Drop ending semicolon (ogr doens't like it)
  sql = sql.replace(/;\s*$/, '');

  Step (

    function fetchColumns() {
      var colsql = 'SELECT * FROM (' + sql + ') as _cartodbsqlapi LIMIT 0';
      pg = new PSQL(dbopts);
      pg.query(colsql, this);
    },
    function findSRS(err, result) {
      if (err) throw err;

      //if ( ! result.rows.length ) throw new Error("Query returns no rows");

      var needSRS = that._needSRS;

      // Skip system columns, find geom column
      for (var i=0; i<result.fields.length; ++i) {
        var field = result.fields[i];
        var k = field.name;
        if ( skipfields.indexOf(k) != -1 ) continue;
        if ( out_format != 'CSV' && k == "the_geom_webmercator" ) continue; // TODO: drop ?
        if ( out_format == 'CSV' ) columns.push(pg.quoteIdentifier(k)+'::text');
        else columns.push(pg.quoteIdentifier(k));

        if ( needSRS ) {
          if ( ! geocol && pg.typeName(field.dataTypeID) == 'geometry' ) {
            geocol = k
          }
        }
      }
      //console.log(columns.join(','));

      if ( ! needSRS || ! geocol ) return null;

      var next = this;

      var qgeocol = pg.quoteIdentifier(geocol);
      var sridsql = 'SELECT ST_Srid(' + qgeocol + ') as srid, GeometryType(' +
                   qgeocol + ') as type FROM (' + sql + ') as _cartodbsqlapi WHERE ' +
                   qgeocol + ' is not null limit 1';

      pg.query(sridsql, function(err, result) {
        if ( err ) { next(err); return; }
        if ( result.rows.length ) {
          var srid = result.rows[0].srid;
          var type = result.rows[0].type;
          next(null, srid, type);
        }
      });

    },
    function spawnDumper(err, srid, type) {
      if (err) throw err;

      var next = this;

      var ogrsql = 'SELECT ' + columns.join(',')
          + ' FROM (' + sql + ') as _cartodbsqlapi';

      var ogrargs = [
        '-f', out_format,
        '-lco', 'ENCODING=UTF-8',
        '-lco', 'LINEFORMAT=CRLF',
        out_filename,
        "PG:host=" + dbhost
         + " port=" + dbport
         + " user=" + dbuser
         + " dbname=" + dbname
         + " password=" + dbpass
         + " tables=fake" // trick to skip query to geometry_columns (private)
                          // in turn breaks knowing SRID with gdal-0.10.1:
                          // http://github.com/CartoDB/CartoDB-SQL-API/issues/110
         + "",
        '-sql', ogrsql
      ];

      if ( srid ) {
        ogrargs.push('-a_srs', 'EPSG:'+srid);
      }

      if ( type ) {
        ogrargs.push('-nlt', type);
      }

      ogrargs.push('-nln', out_layername);

      var child = spawn(ogr2ogr, ogrargs);

/*
console.log('ogr2ogr ' + _.map(ogrargs, function(x) { return "'" + x + "'"; }).join(' '));
*/

      var stdout = '';
      child.stdout.on('data', function(data) {
        stdout += data;
        //console.log('stdout: ' + data);
      });

      var stderr;
      var logErrPat = new RegExp(/^ERROR/);
      child.stderr.on('data', function(data) {
        data = data.toString(); // know of a faster way ?
        // Store only the first ERROR line
        if ( ! stderr && data.match(logErrPat) ) stderr = data;
        console.log('ogr2ogr stderr: ' + data);
      });

      child.on('exit', function(code) {
        if ( code ) {
          console.log("ogr2ogr exited with code " + code);
          var emsg = stderr ? stderr.split('\n')[0] : ( "unknown ogr2ogr error (code " + code + ")" );
          // TODO: add more info about this error ?
          //if ( RegExp(/attempt to write non-.*geometry.*to.*type shapefile/i).exec(emsg) )
          next(new Error(emsg));
        } else {
          next(null);
        }
      });
    },
    function finish(err) {
      callback(err, out_filename);
    }
  );
};

ogr.prototype.toOGR_SingleFile = function(options, fmt, callback) {

  var dbname = options.dbopts.dbname;
  var user_id = options.dbopts.user;
  var gcol = options.gcol;
  var sql = options.sql;
  var skipfields = options.skipfields;
  var ext = this._fileExtension;
  var layername = options.filename;

  var tmpdir = global.settings.tmpDir || '/tmp';
  var reqKey = [ fmt, dbname, user_id, gcol, this.generateMD5(layername), this.generateMD5(sql) ].concat(skipfields).join(':');
  var outdirpath = tmpdir + '/sqlapi-' + process.pid + '-' + reqKey;
  var dumpfile = outdirpath + ':cartodb-query.' + ext;

  // TODO: following tests:
  //  - fetch query with no "the_geom" column
  this.toOGR(options, fmt, dumpfile, callback);
};

ogr.prototype.sendResponse = function(opts, callback) {
  var next = callback;
  var reqKey = this.getKey(opts);
  var qElem = new ExportRequest(opts.sink, callback);
  var baking = bakingExports[reqKey];
  if ( baking ) {
    baking.req.push( qElem );
  } else {
    baking = bakingExports[reqKey] = { req: [ qElem ] };
    this.generate(opts, function(err, dumpfile) {
      Step (
        function sendResults() {
          var nextPipe = function(finish) {
            var r = baking.req.shift();
            if ( ! r ) { finish(null); return; }
            r.sendFile(err, dumpfile, function() {
              nextPipe(finish);
            });
          }

          if ( ! err ) nextPipe(this);
          else {
            _.each(baking.req, function(r) {
              r.cb(err);
            });
            return true;
          }
        },
        function cleanup(err) {
          delete bakingExports[reqKey];

          // unlink dump file (sync to avoid race condition)
          console.log("removing", dumpfile);
          try { fs.unlinkSync(dumpfile); }
          catch (e) {
            if ( e.code != 'ENOENT' ) {
              console.log("Could not unlink dumpfile " + dumpfile + ": " + e);
            }
          }
        }
      );
    })
  }
  return;
};

// TODO: put in an ExportRequest.js ----- {

function ExportRequest(ostream, callback) {
  this.cb = callback;
  this.ostream = ostream;
  this.istream = null;
  this.canceled = false;

  var that = this;

  this.ostream.on('close', function() {
    //console.log("Request close event, qElem.stream is " + qElem.stream);
    that.canceled = true;
    if ( that.istream ) {
      that.istream.destroy();
    }
  });
}

ExportRequest.prototype.sendFile = function (err, filename, callback) {
  var that = this;
  if ( ! this.canceled ) {
    //console.log("Creating readable stream out of dumpfile");
    this.istream = fs.createReadStream(filename)
    .on('open', function(fd) {
      that.istream.pipe(that.ostream);
      callback();
    })
    .on('error', function(e) {
      console.log("Can't send response: " + e);
      that.ostream.end();
      callback();
    });
  } else {
    //console.log("Response was canceled, not streaming the file");
    callback();
  }
  this.cb();
}

//------ }

module.exports = ogr;
<|endoftext|>


<commit_before>const amqp = require('amqplib'),
      Promise = require('bluebird'),
      messageCoding = require('./message-coding'),
      messageSerializing = require('./message-serializing'),
      R = require('ramda');

function enqueuePromised(chan) {
  return new Promise((resolve, reject) => {
    try {
      chan.sendToQueue.apply(chan, R.tail(arguments))
        ? resolve()
        : chan.once('drain', () => resolve());
    } catch (err) {
      reject(err);
    }
  }
  );
};

const skipMessages = (chan, count) => {
  if (count <= 0) {
    return Promise.resolve(true);
  }

  return chan.get()
    .then(R.ifElse(R.identity, () => skipMessages(chan, --count), Promise.resolve));
};

class RabbitMqClient {
  constructor() { }

  connect(host, queue) {
    this.host = host;
    this.queue = queue;

    this.connection = amqp.connect(this.host)
      .then((conn) => conn.createChannel()
        .then(chan => {
          chan.on('close', () => {
            conn.close();
          });
          return chan.checkQueue(this.queue).then(() => chan);
        })
      );

    this.messages = {};
    this.currentMessageId = -1;
    return this;
  }

  disconnect() {
    return new Promise((resolve, reject) => {
      this.connection
      .then(chan => {
        chan.connection.once('close', resolve);
        chan.connection.once('error', reject);
        chan.close()
      });
    });
  }

  skipMessages(count) {
    return this.connection
      .then(chan => skipMessages(chan, count));
  }

  nextMessage() {
    return this.connection
      .then(chan => chan.get(this.queue))
      .then(R.when(R.complement(R.isNil), R.pipe(messageCoding.decode, messageSerializing.deserialize)))
      .then(message => {
        if (message) {
          let id = ++this.currentMessageId;
          this.messages[id] = { id, queue: this.queue, host: this.host, message };
        }
        return message;
      });
  }

  enqueueMessage(msg, queue) {
    return this.connection
      .then(chan => enqueuePromised(chan, queue, R.pipe(messageSerializing.serialize, messageCoding.encode)(msg.content), msg.properties));
  }

  getMessage(id) {
    return R.pipe(
      R.defaultTo(this.currentMessageId),
      R.prop(R.__, this.messages),
      R.ifElse(R.isNil, Promise.reject, R.pipe(
        R.prop('message'),
        Promise.resolve
      ))
    )(id);
  }

  getMessages() {
    return Promise.resolve(this.messages);
  }

  addMessage(message) {
    let id = ++this.currentMessageId;
    this.messages[id] = { id, queue: '', host: '', message };
    return Promise.resolve(this.messages[id]);
  }

  ack(msg) {
    return this.connection
      .then(chan => chan.ack(msg));
  }
}

module.exports = RabbitMqClient;
<commit_msg>Fix enqueue message content<commit_after>const amqp = require('amqplib'),
      Promise = require('bluebird'),
      messageCoding = require('./message-coding'),
      messageSerializing = require('./message-serializing'),
      R = require('ramda');

function enqueuePromised(chan) {
  return new Promise((resolve, reject) => {
    try {
      chan.sendToQueue.apply(chan, R.tail(arguments))
        ? resolve()
        : chan.once('drain', () => resolve());
    } catch (err) {
      reject(err);
    }
  }
  );
};

const skipMessages = (chan, count) => {
  if (count <= 0) {
    return Promise.resolve(true);
  }

  return chan.get()
    .then(R.ifElse(R.identity, () => skipMessages(chan, --count), Promise.resolve));
};

class RabbitMqClient {
  constructor() { }

  connect(host, queue) {
    this.host = host;
    this.queue = queue;

    this.connection = amqp.connect(this.host)
      .then((conn) => conn.createChannel()
        .then(chan => {
          chan.on('close', () => {
            conn.close();
          });
          return chan.checkQueue(this.queue).then(() => chan);
        })
      );

    this.messages = {};
    this.currentMessageId = -1;
    return this;
  }

  disconnect() {
    return new Promise((resolve, reject) => {
      this.connection
      .then(chan => {
        chan.connection.once('close', resolve);
        chan.connection.once('error', reject);
        chan.close()
      });
    });
  }

  skipMessages(count) {
    return this.connection
      .then(chan => skipMessages(chan, count));
  }

  nextMessage() {
    return this.connection
      .then(chan => chan.get(this.queue))
      .then(R.when(R.complement(R.isNil), R.pipe(messageCoding.decode, messageSerializing.deserialize)))
      .then(message => {
        if (message) {
          let id = ++this.currentMessageId;
          this.messages[id] = { id, queue: this.queue, host: this.host, message };
        }
        return message;
      });
  }

  enqueueMessage(msg, queue) {
    return this.connection
      .then(chan => {
        return enqueuePromised(chan, queue, R.pipe(messageSerializing.serialize, messageCoding.encode, R.prop('content'))(msg), msg.properties)
      });
  }

  getMessage(id) {
    return R.pipe(
      R.defaultTo(this.currentMessageId),
      R.prop(R.__, this.messages),
      R.ifElse(R.isNil, Promise.reject, R.pipe(
        R.prop('message'),
        Promise.resolve
      ))
    )(id);
  }

  getMessages() {
    return Promise.resolve(this.messages);
  }

  addMessage(message) {
    let id = ++this.currentMessageId;
    this.messages[id] = { id, queue: '', host: '', message };
    return Promise.resolve(this.messages[id]);
  }

  ack(msg) {
    return this.connection
      .then(chan => chan.ack(msg));
  }
}

module.exports = RabbitMqClient;
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Route.extend({
  // An array with the query of the current tutorial step. First one
  // is at index `1` to have nice URLs and easier logic
  queries: null,

  // An array with the description of the current tutorial step. First one
  // is at index `1` to have nice URLs and easier logic
  descriptions: null,

  // An array with the title of the current tutorial step. First one
  // is at index `1` to have nice URLs and easier logic
  titles: null,

  // A HTML block that contains the JSON schema description
  schema: null,

  beforeModel() {
    return fetch('/assets/html/tutorial.html').then(response => {
      return response.text();
    }).then(text => {
      // The returned value is a full HTML document. If the dom is created
      // with just calling `$(text)`, then the top level elements would be the
      // children of the `<body>` (and not the full HTML tree as expected).
      // This makes traversing the DOM quite difficult. Hence append it to an
      // newly created `<body>` tag, so that the traversal works as expected.
      // For more information see https://api.jquery.com/jQuery/#jQuery2 about
      // "passing in complex HTML".
      const dom = Ember.$('<body>').append(text);

      // Extract code tags that contain the queries
      const codeTags = dom.find('#tutorial code');

      // Remove the code tags from the DOM as we only want the descriptions
      codeTags.remove();

      // Extract the article tags that contain the descriptions
      const articleTags = dom.find('#tutorial article');

      // Extract the h3 tags that contain the titles
      const h3Tags = dom.find('#tutorial h3');

      // The first element is a dummy element so that we can start
      // enumerating the tutorial steps starting with 1.
      let queries = [null];
      let descriptions = [null];
      let titles = [null];
      const schema = dom.find('#json-schema code').text().trim();

      // Re-combine the queries and the descriptions as JSON
      articleTags.each((ii, articleTag) => {
        queries.push(Ember.$(codeTags[ii]).text().trim());
        descriptions.push(articleTag);
        titles.push(Ember.$(h3Tags[ii]).text().trim());
      });
      this.set('queries', queries);
      this.set('descriptions', descriptions);
      this.set('titles', titles);
      this.set('schema', schema);
    });
  },
  model() {
    return {
      queries: this.get('queries'),
      descriptions: this.get('descriptions'),
      titles: this.get('titles'),
      schema: this.get('schema')
    };
  }
});
<commit_msg>Using jQuery for fetching the tutorial<commit_after>import Ember from 'ember';

export default Ember.Route.extend({
  // An array with the query of the current tutorial step. First one
  // is at index `1` to have nice URLs and easier logic
  queries: null,

  // An array with the description of the current tutorial step. First one
  // is at index `1` to have nice URLs and easier logic
  descriptions: null,

  // An array with the title of the current tutorial step. First one
  // is at index `1` to have nice URLs and easier logic
  titles: null,

  // A HTML block that contains the JSON schema description
  schema: null,

  beforeModel() {
    return Ember.$.get('/assets/html/tutorial.html').done(text => {
      // The returned value is a full HTML document. If the dom is created
      // with just calling `$(text)`, then the top level elements would be the
      // children of the `<body>` (and not the full HTML tree as expected).
      // This makes traversing the DOM quite difficult. Hence append it to an
      // newly created `<body>` tag, so that the traversal works as expected.
      // For more information see https://api.jquery.com/jQuery/#jQuery2 about
      // "passing in complex HTML".
      const dom = Ember.$('<body>').append(text);

      // Extract code tags that contain the queries
      const codeTags = dom.find('#tutorial code');

      // Remove the code tags from the DOM as we only want the descriptions
      codeTags.remove();

      // Extract the article tags that contain the descriptions
      const articleTags = dom.find('#tutorial article');

      // Extract the h3 tags that contain the titles
      const h3Tags = dom.find('#tutorial h3');

      // The first element is a dummy element so that we can start
      // enumerating the tutorial steps starting with 1.
      let queries = [null];
      let descriptions = [null];
      let titles = [null];
      const schema = dom.find('#json-schema code').text().trim();

      // Re-combine the queries and the descriptions as JSON
      articleTags.each((ii, articleTag) => {
        queries.push(Ember.$(codeTags[ii]).text().trim());
        descriptions.push(articleTag);
        titles.push(Ember.$(h3Tags[ii]).text().trim());
      });
      this.set('queries', queries);
      this.set('descriptions', descriptions);
      this.set('titles', titles);
      this.set('schema', schema);
    });
  },
  model() {
    return {
      queries: this.get('queries'),
      descriptions: this.get('descriptions'),
      titles: this.get('titles'),
      schema: this.get('schema')
    };
  }
});
<|endoftext|>


<commit_before>var fs = require('fs');
var cl_template = require('./client.js');
var game_template = require('./../Game/game.js');
var Clients = [];
var Games = [];
var NamedClients = [];
var SciNames;
var io;

fs.readFile(__dirname + '/scientist-names.json','utf8', function(err, data)
{
	if (err)
	{
		console.Error("Cannot load JSON file for scientist-names");
		return;
	}
	SciNames = JSON.parse(data);
});

exports.init = function(ioContext)
{
	io = ioContext;
};

function request(socket, data)
{
	if ( 'undefined' == typeof socket )
	{
		return;
	}

	var res = null;
	if ( 'undefined' == typeof data || 'undefined' == typeof data.cmd )
	{
		res = {
			approved: false,
			data: "Error invaild request",
			id: data.id
		};
	}

	else
	{
		var clientContext = Clients[socket.id];
		if (clientContext.isPlaying)
			return;

		switch ( data.cmd )
		{
			//Create Game
			case 'C':
				res = createGame(clientContext, data.id);
				break;

			//Leave Game
			case 'L':
				res = leaveGame(clientContext, data.id);
				break;

			//Join Game
			case 'J':
				res = joinGame(clientContext, data.data, data.id);
				break;

			//Set Ready
			case 'R':
				res = setReady(clientContext, data.id);
				break;

			//Set Wait
			case 'W':
				res = setWait(clientContext, data.id);
				break;

			//Set Name
			case 'N':
				res = setName(clientContext, data.data, data.id);
				break;

			case 'I':
				res = lobbyInit(clientContext, data.id);
			break;

			default:
				res = {
					approved: false,
					data: "Error cannot determine command",
					id: data.id
				};
				break;
		}
	}
	socket.emit( 'lobby', res );
}

// Helper for adding clients to the lobby.
// Should be done on connect
exports.AddClient = function(socket)
{
	Clients[socket.id] = new cl_template.CreateClient(socket);

	socket.on('lobby', function(data)
	{
		request(socket, data);
	});

	socket.on('disconnect', function()
	{
		onDisconnect(socket);
	});
};

exports.GetClients = function()
{
	return Clients;
};

function createGame(clientContext, reqId)
{
	//Check if the player is already in a game
	if (clientContext.isInGame === true)
	{
		return {
			approved: false,
			data: "Cannot create a game if you are in a game"

		};
	}

	//Check if the play has a name
	if('undefined' == typeof clientContext.name || clientContext.name === null)
	{
		return {
			approved: false,
			data: "Cannot create a game. client name is invaild",
			id: reqId
		};
	}

	var game = game_template.CreateGame(clientContext);

	Games[game.gameName] = game;

	pushGameCreated(game);
	pushPlayerUpdate(clientContext);

	var ret = {};
	ret.approved = true;
	ret.id = reqId;
	ret.data = {};
	ret.data.players = [];
	var player = {
		name: clientContext.name,
		state: clientContext.isReady
	};
	ret.data.players.push(player);
	return ret;
}

function leaveGame(clientContext, reqId)
{
	if(clientContext.isInGame === false)
	{
		return{
			approved: false,
			data: "Cannot leave a game if you are not in a game",
			id: reqId
		};
	}

	if ('undefined' == typeof clientContext.name || clientContext.name === null)
	{
		return {
			approved: false,
			data: "Cannot leave game. client name is invaild",
			id: reqId
		};
	}

	var gameContext;

	for (var key in Games)
	{
		if (Games.hasOwnProperty(key))
		{
			if (Games[key].id === clientContext.gameId)
			{
				gameContext = Games[key];
			}
		}
	}

	var isGameDeleted = gameContext.leaveGame(clientContext);
	if (isGameDeleted === true)
	{
		pushGameClosed(gameContext);
		delete Games[gameContext.gameName];
	}
	else
	{
		pushGameUpdated(gameContext);
	}

	pushPlayerUpdate(clientContext);
	return{
		approved: true,
		id: reqId
	};
}

function joinGame(clientContext, data, reqId)
{
	if (clientContext.isInGame === true)
	{
		return {
			approved: false,
			data: "Cannot join a game if you are in a game",
			id: reqId
		};
	}

	if ('undefined' == typeof clientContext.name || clientContext.name === null)
	{
		return{
			approved: false,
			data: "Cannot join a game if you do not have a name",
			id: reqId
		};
	}

	var gameContext = Games[data];

	if ('undefined' == typeof gameContext)
	{
		return{
			approved: false,
			data: "You dun fucked up"
		};
	}

	if (gameContext.joinGame(clientContext) === true)
	{
		clientContext.isInGame = true;
		clientContext.gameId = gameContext.gameId;

		pushGameUpdated(gameContext);

		return {
			approved: true,
			id: reqId
		};
	}
	else
		return{
			approved: false,
			data: "Game is either full or dosent exist anymore",
			id: reqId
		};
}

function setReady(clientContext, reqId)
{
	if (clientContext.isInGame === false)
	{
		return{
			approved: false,
			data: "Cannot ready whne you are not in a game",
			id: reqId
		};
	}

	if ('undefined' == typeof clientContext.name || clientContext.name === null)
	{
		return {
			approved: false,
			data: "Cannot ready if you do not have a name",
			id: reqId
		};
	}

	if (clientContext.isReady === true)
	{
		return {
			approved: false,
			data: "Cannot ready if you are already ready",
			id: reqId
		};
	}

	clientContext.isReady = true;

	var gameContext = {};

	for (var key in Games)
	{
		if (Games.hasOwnProperty(key))
		{
			if (Games[key].id === clientContext.gameId)
			{
				gameContext = Games[key];
			}
		}
	}

	pushGameUpdated(gameContext);

	return {
		approved: true,
		id: reqId
	};
}

function setWait(clientContext, reqId)
{
	if (clientContext.isInGame === false)
	{
		return {
			approved: false,
			data: "Must be in a game to be waiting",
			id: reqId
		};
	}

	if ('undefined' == typeof clientContext.name || clientContext.name === null)
	{
		return {
			approved: false,
			data: "Cannot wait if you do not have a name",
			id: reqId
		};
	}

	if (clientContext.isReady === false)
	{
		return {
			approved: false,
			data: "Cannot wait if you are already waiting",
			id: reqId
		};
	}

	clientContext.isReady = false;

	var gameContext;

	for (var key in Games)
	{
		if (Games.hasOwnProperty(key))
		{
			if (Games[key].id === clientContext.gameId)
			{
				gameContext = Games[key];
			}
		}
	}

	pushGameUpdated(gameContext);

	return {
		approved: true,
		id: reqId
	};
}

function setName(clientContext, data, reqId)
{
	if (clientContext.isInGame === true)
	{
		return {
			approved: false,
			data: "Cannot change your name if you are in a game",
			id: reqId
		};
	}

	var isPlayerCreated = false;

	if (clientContext.name !== "")
	{
		if ('undefined' != typeof NamedClients[clientContext.name])
			delete NamedClients[clientContext.name];

	}
	else
	{
		isPlayerCreated = true;
	}

	//Change the name
	if (data === '')
	{

		var name;
		do
		{
			var potentialName = SciNames[Math.floor(Math.random()*SciNames.length)];

			if ('undefined' == typeof NamedClients[potentialName])
			{
				name = potentialName;
				break;
			}

		} while (true);

		clientContext.name = name;
		NamedClients[name] = clientContext;

		if(isPlayerCreated)
			pushPlayerCreated(clientContext);
		else
			pushPlayerUpdate(clientContext);

		return{
			approved: true,
			data: name,
			id: reqId
		};
	}

	else
	{

		if ('undefined' != typeof NamedClients[data])
		{
			return {
				approved:false,
				data: "Name is already taken",
				id: reqId
			};
		}

		clientContext.name = data;
		NamedClients[data] = clientContext;

		if (isPlayerCreated)
			pushPlayerCreated(clientContext);
		else
			pushPlayerUpdate(clientContext);

		return {
			approved: true,
			data: data,
			id: reqId
		};
	}
}

function lobbyInit(clientContext, reqId)
{
	if (clientContext.isInGame === true)
	{
		return {
			approved: false,
			data: "something very bad happened to the client state. contact help fast",
			id: reqId
		};
	}

	if ('undefined' == typeof clientContext.name || clientContext.name === null)
	{
		return {
			approved: false,
			data: "get the lobby if you do not have a name",
			id: reqId
		};
	}
	var ret = {};
	ret.approved = true;
	ret.data = {};
	ret.data.players = [];

	for (var key in NamedClients)
	{
		if(NamedClients.hasOwnProperty(key))
		{
			var player = {};
			player.name = NamedClients[key].name;
			player.state = (NamedClients[key].isInGame ? 'Playing' : 'Available');
			ret.data.players.push(player);
		}
	}

	ret.data.games = [];

	for (key in Games)
	{
		if(Games.hasOwnProperty(key))
		{
			var game = {};
			game.players = [];
			for (var j = 0; j < Games[key].players.length; j++)
			{
				var gamePlayer = {};
				gamePlayer.name = Games[key].players[j].name;
				gamePlayer.ready = Games[key].players[j].isReady;
				game.players.push(gamePlayer);
			}
			ret.data.games.push(game);
		}
	}

	ret.id = reqId;
	clientContext.isInitalized = true;
	pushPlayerCreated(clientContext);
	return ret;
}

function onDisconnect(socket)
{
	var client = Clients[socket.id];
	pushPlayerDisconnect(client);
	var name = client.name;

	if (client.isInGame === true)
	{
		var gameContext;

		for (var key in Games)
		{
			if (Games.hasOwnProperty(key))
			{
				if (Games[key].id === client.gameId)
				{
					gameContext0 = Games[key];
				}
			}
		}
		if('undefined' != typeof gameContext)
		{
			var isGameDeleted = gameContext.leaveGame(client);
			if (isGameDeleted === true)
			{
				pushGameClosed(gameContext);
				delete Games[gameContext.gameName];
			}
			else
			{
				pushGameUpdated(gameContext);
			}
			pushGameClosed(gameContext);
			delete Games[gameContext.gameName];
		}

	}

	delete Clients[socket.id];
	delete NamedClients[name];
}

function pushPlayerCreated(clientContext)
{

	var req = {
		cmd: "PC",
		data: {
			name: clientContext.name,
			state: (clientContext.isInGame ? 'Playing' : 'Available')
		}
	};

	sendPushRequestToAllBut(req, clientContext);
}

function pushPlayerDisconnect(clientContext)
{
	var req = {
		cmd: 'PR',
		data: clientContext.name
	};

	sendPushRequestToAllBut(req, clientContext);
}

function pushPlayerUpdate(clientContext)
{
	var req = {
		cmd: 'PU',
		data: {
			name: clientContext.name,
			state: (clientContext.isInGame ? 'Playing' : 'Available')
		}
	};

	sendPushRequestToAllBut(req, clientContext);
}

function pushGameCreated(gameContext)
{

	var game = {};
	game.players = [];

	for (var i = 0; i < gameContext.players.length; i++)
	{
		var player = {
			name: gameContext.players[i].name,
			ready: gameContext.isReady
		};
		game.players.push(player);
	}

	var req = {
		cmd: 'GC',
		data: game
	};

	sendPushRequestToAllBut(req, gameContext.players[0]);
}

function pushGameClosed(gameContext)
{
	var req = {
		cmd: 'GR',
		data: gameContext.gameName
	};

	sendPushRequestToAllBut(req, gameContext.players[0]);
}

function pushGameUpdated(gameContext)
{
	var game = {};
	game.players = [];

	for (var i = 0; i < gameContext.players.length; i++)
	{
		var player = {
			name: gameContext.players[i].name,
			ready: gameContext.isReady
		};
		game.players.push(player);
	}

	var req = {
		cmd: 'GU',
		data: game
	};

	sendPushRequestToAllBut(req, gameContext.players[0]);
}

function sendPushRequestToAllBut(req, clientContext)
{
	for (var key in NamedClients)
	{
		if (NamedClients.hasOwnProperty(key))
		{
			if (NamedClients[key] == clientContext)
				continue;
			NamedClients[key].cl_socket.emit('lobby', req);
		}
	}
}<commit_msg>fixed leaving and joining of games on the server side<commit_after>var fs = require('fs');
var cl_template = require('./client.js');
var game_template = require('./../Game/game.js');
var Clients = [];
var Games = [];
var NamedClients = [];
var SciNames;
var io;

fs.readFile(__dirname + '/scientist-names.json','utf8', function(err, data)
{
	if (err)
	{
		console.Error("Cannot load JSON file for scientist-names");
		return;
	}
	SciNames = JSON.parse(data);
});

exports.init = function(ioContext)
{
	io = ioContext;
};

function request(socket, data)
{
	if ( 'undefined' == typeof socket )
	{
		return;
	}

	var res = null;
	if ( 'undefined' == typeof data || 'undefined' == typeof data.cmd )
	{
		res = {
			approved: false,
			data: "Error invaild request",
			id: data.id
		};
	}

	else
	{
		var clientContext = Clients[socket.id];
		if (clientContext.isPlaying)
			return;

		switch ( data.cmd )
		{
			//Create Game
			case 'C':
				res = createGame(clientContext, data.id);
				break;

			//Leave Game
			case 'L':
				res = leaveGame(clientContext, data.id);
				break;

			//Join Game
			case 'J':
				res = joinGame(clientContext, data.data, data.id);
				break;

			//Set Ready
			case 'R':
				res = setReady(clientContext, data.id);
				break;

			//Set Wait
			case 'W':
				res = setWait(clientContext, data.id);
				break;

			//Set Name
			case 'N':
				res = setName(clientContext, data.data, data.id);
				break;

			case 'I':
				res = lobbyInit(clientContext, data.id);
			break;

			default:
				res = {
					approved: false,
					data: "Error cannot determine command",
					id: data.id
				};
				break;
		}
	}
	socket.emit( 'lobby', res );
}

// Helper for adding clients to the lobby.
// Should be done on connect
exports.AddClient = function(socket)
{
	Clients[socket.id] = new cl_template.CreateClient(socket);

	socket.on('lobby', function(data)
	{
		request(socket, data);
	});

	socket.on('disconnect', function()
	{
		onDisconnect(socket);
	});
};

exports.GetClients = function()
{
	return Clients;
};

function createGame(clientContext, reqId)
{
	//Check if the player is already in a game
	if (clientContext.isInGame === true)
	{
		return {
			approved: false,
			data: "Cannot create a game if you are in a game"

		};
	}

	//Check if the play has a name
	if('undefined' == typeof clientContext.name || clientContext.name === null)
	{
		return {
			approved: false,
			data: "Cannot create a game. client name is invaild",
			id: reqId
		};
	}

	var game = game_template.CreateGame(clientContext);

	Games[game.gameName] = game;

	pushGameCreated(game);
	pushPlayerUpdate(clientContext);

	var ret = {};
	ret.approved = true;
	ret.id = reqId;
	ret.data = {};
	ret.data.players = [];
	var player = {
		name: clientContext.name,
		state: clientContext.isReady
	};
	ret.data.players.push(player);
	return ret;
}

function leaveGame(clientContext, reqId)
{
	if(clientContext.isInGame === false)
	{
		return{
			approved: false,
			data: "Cannot leave a game if you are not in a game",
			id: reqId
		};
	}

	if ('undefined' == typeof clientContext.name || clientContext.name === null)
	{
		return {
			approved: false,
			data: "Cannot leave game. client name is invaild",
			id: reqId
		};
	}

	var gameContext;

	for (var key in Games)
	{
		if (Games.hasOwnProperty(key))
		{
			if (Games[key].id === clientContext.gameId)
			{
				gameContext = Games[key];
			}
		}
	}

	var isGameDeleted = gameContext.leaveGame(clientContext);
	if (isGameDeleted === true)
	{
		pushGameClosed(gameContext);
		delete Games[gameContext.gameName];
	}
	else
	{
		pushGameUpdated(gameContext);
	}

	pushPlayerUpdate(clientContext);
	return{
		approved: true,
		id: reqId
	};
}

function joinGame(clientContext, data, reqId)
{
	if (clientContext.isInGame === true)
	{
		return {
			approved: false,
			data: "Cannot join a game if you are in a game",
			id: reqId
		};
	}

	if ('undefined' == typeof clientContext.name || clientContext.name === null)
	{
		return{
			approved: false,
			data: "Cannot join a game if you do not have a name",
			id: reqId
		};
	}

	var gameContext = Games[data];

	if ('undefined' == typeof gameContext)
	{
		return{
			approved: false,
			data: "You dun fucked up"
		};
	}

	if (gameContext.joinGame(clientContext) === true)
	{
		clientContext.isInGame = true;
		clientContext.gameId = gameContext.id;

		pushGameUpdated(gameContext);

		return {
			approved: true,
			id: reqId
		};
	}
	else
		return{
			approved: false,
			data: "Game is either full or dosent exist anymore",
			id: reqId
		};
}

function setReady(clientContext, reqId)
{
	if (clientContext.isInGame === false)
	{
		return{
			approved: false,
			data: "Cannot ready whne you are not in a game",
			id: reqId
		};
	}

	if ('undefined' == typeof clientContext.name || clientContext.name === null)
	{
		return {
			approved: false,
			data: "Cannot ready if you do not have a name",
			id: reqId
		};
	}

	if (clientContext.isReady === true)
	{
		return {
			approved: false,
			data: "Cannot ready if you are already ready",
			id: reqId
		};
	}

	clientContext.isReady = true;

	var gameContext = {};

	for (var key in Games)
	{
		if (Games.hasOwnProperty(key))
		{
			if (Games[key].id === clientContext.gameId)
			{
				gameContext = Games[key];
			}
		}
	}

	pushGameUpdated(gameContext);

	return {
		approved: true,
		id: reqId
	};
}

function setWait(clientContext, reqId)
{
	if (clientContext.isInGame === false)
	{
		return {
			approved: false,
			data: "Must be in a game to be waiting",
			id: reqId
		};
	}

	if ('undefined' == typeof clientContext.name || clientContext.name === null)
	{
		return {
			approved: false,
			data: "Cannot wait if you do not have a name",
			id: reqId
		};
	}

	if (clientContext.isReady === false)
	{
		return {
			approved: false,
			data: "Cannot wait if you are already waiting",
			id: reqId
		};
	}

	clientContext.isReady = false;

	var gameContext;

	for (var key in Games)
	{
		if (Games.hasOwnProperty(key))
		{
			if (Games[key].id === clientContext.gameId)
			{
				gameContext = Games[key];
			}
		}
	}

	pushGameUpdated(gameContext);

	return {
		approved: true,
		id: reqId
	};
}

function setName(clientContext, data, reqId)
{
	if (clientContext.isInGame === true)
	{
		return {
			approved: false,
			data: "Cannot change your name if you are in a game",
			id: reqId
		};
	}

	var isPlayerCreated = false;

	if (clientContext.name !== "")
	{
		if ('undefined' != typeof NamedClients[clientContext.name])
			delete NamedClients[clientContext.name];

	}
	else
	{
		isPlayerCreated = true;
	}

	//Change the name
	if (data === '')
	{

		var name;
		do
		{
			var potentialName = SciNames[Math.floor(Math.random()*SciNames.length)];

			if ('undefined' == typeof NamedClients[potentialName])
			{
				name = potentialName;
				break;
			}

		} while (true);

		clientContext.name = name;
		NamedClients[name] = clientContext;

		if(isPlayerCreated)
			pushPlayerCreated(clientContext);
		else
			pushPlayerUpdate(clientContext);

		return{
			approved: true,
			data: name,
			id: reqId
		};
	}

	else
	{

		if ('undefined' != typeof NamedClients[data])
		{
			return {
				approved:false,
				data: "Name is already taken",
				id: reqId
			};
		}

		clientContext.name = data;
		NamedClients[data] = clientContext;

		if (isPlayerCreated)
			pushPlayerCreated(clientContext);
		else
			pushPlayerUpdate(clientContext);

		return {
			approved: true,
			data: data,
			id: reqId
		};
	}
}

function lobbyInit(clientContext, reqId)
{
	if (clientContext.isInGame === true)
	{
		return {
			approved: false,
			data: "something very bad happened to the client state. contact help fast",
			id: reqId
		};
	}

	if ('undefined' == typeof clientContext.name || clientContext.name === null)
	{
		return {
			approved: false,
			data: "get the lobby if you do not have a name",
			id: reqId
		};
	}
	var ret = {};
	ret.approved = true;
	ret.data = {};
	ret.data.players = [];

	for (var key in NamedClients)
	{
		if(NamedClients.hasOwnProperty(key))
		{
			var player = {};
			player.name = NamedClients[key].name;
			player.state = (NamedClients[key].isInGame ? 'Playing' : 'Available');
			ret.data.players.push(player);
		}
	}

	ret.data.games = [];

	for (key in Games)
	{
		if(Games.hasOwnProperty(key))
		{
			var game = {};
			game.players = [];
			for (var j = 0; j < Games[key].players.length; j++)
			{
				var gamePlayer = {};
				gamePlayer.name = Games[key].players[j].name;
				gamePlayer.ready = Games[key].players[j].isReady;
				game.players.push(gamePlayer);
			}
			ret.data.games.push(game);
		}
	}

	ret.id = reqId;
	clientContext.isInitalized = true;
	pushPlayerCreated(clientContext);
	return ret;
}

function onDisconnect(socket)
{
	var client = Clients[socket.id];
	pushPlayerDisconnect(client);
	var name = client.name;

	if (client.isInGame === true)
	{
		var gameContext;

		for (var key in Games)
		{
			if (Games.hasOwnProperty(key))
			{
				if (Games[key].id === client.gameId)
				{
					gameContext = Games[key];
				}
			}
		}
		if('undefined' != typeof gameContext)
		{
			var isGameDeleted = gameContext.leaveGame(client);
			if (isGameDeleted === true)
			{
				pushGameClosed(gameContext);
				delete Games[gameContext.gameName];
			}
			else
			{
				pushGameUpdated(gameContext);
			}
		}

	}

	delete Clients[socket.id];
	delete NamedClients[name];
}

function pushPlayerCreated(clientContext)
{

	var req = {
		cmd: "PC",
		data: {
			name: clientContext.name,
			state: (clientContext.isInGame ? 'Playing' : 'Available')
		}
	};

	sendPushRequestToAllBut(req, clientContext);
}

function pushPlayerDisconnect(clientContext)
{
	var req = {
		cmd: 'PR',
		data: clientContext.name
	};

	sendPushRequestToAllBut(req, clientContext);
}

function pushPlayerUpdate(clientContext)
{
	var req = {
		cmd: 'PU',
		data: {
			name: clientContext.name,
			state: (clientContext.isInGame ? 'Playing' : 'Available')
		}
	};

	sendPushRequestToAllBut(req, clientContext);
}

function pushGameCreated(gameContext)
{

	var game = {};
	game.players = [];

	for (var i = 0; i < gameContext.players.length; i++)
	{
		var player = {
			name: gameContext.players[i].name,
			ready: gameContext.isReady
		};
		game.players.push(player);
	}

	var req = {
		cmd: 'GC',
		data: game
	};

	sendPushRequestToAllBut(req, gameContext.players[0]);
}

function pushGameClosed(gameContext)
{
	var req = {
		cmd: 'GR',
		data: gameContext.gameName
	};

	sendPushRequestToAllBut(req, null);
}

function pushGameUpdated(gameContext)
{
	var game = {};
	game.players = [];

	for (var i = 0; i < gameContext.players.length; i++)
	{
		var player = {
			name: gameContext.players[i].name,
			ready: gameContext.players[i].isReady
		};
		game.players.push(player);
	}

	var req = {
		cmd: 'GU',
		data: game
	};

	sendPushRequestToAllBut(req, null);
}

function sendPushRequestToAllBut(req, clientContext)
{
	for (var key in NamedClients)
	{
		if (NamedClients.hasOwnProperty(key))
		{
			if (NamedClients[key] == clientContext)
				continue;
			NamedClients[key].cl_socket.emit('lobby', req);
		}
	}
}<|endoftext|>


<commit_before>/**
 * Shortest Path in Maze II
 *
 * Given a matrix represents a maze, X is the starting point, Y is the destination,
 * # is wall, now you can walk in four directions: up, down, left, right.
 * Now you are allowed to break at most k walls, what is the minimum steps to get
 * to Y from X?
 *
 * For example, we have the following maze, and you can break at most 3 walls.
 *
 *     . X . # . .
 *     . # . . # .
 *     . # . . . .
 *     . # # . . .
 *     . Y . . . .
 *     . . . . . #
 *
 * the minimum steps to reach Y from X is 4.
 *
 * Normally we run BFS in a flat level, the idea is to run the BFS in a k-level matrix.
 * Each level is connected thru the wall, there are k levels, each time when we break a wall,
 * we go to the next level, and we can continue to perform BFS from that point. One challenging
 * part is that we need to use a 3-dimensional array to help us check whether a point is visited.
 *
 * Time complexity: O(mnk)
 * note, if k is constant, then time complexity is O(mn)
 */

/**
 * @param {string[][]} maze
 * @param {number[]} px
 * @param {number} k
 */
const shortestPath = (maze, px, k) => {
  const dx = [-1, 1, 0, 0];
  const dy = [0, 0, -1, 1];

  const m = maze.length;
  const n = maze[0].length;

  // Push {x, y, z} to the queue
  // z is 0 by default, represents the layer where no wall is broken
  const queue = [[...px, 0], null];

  // Create a { m x n x k } three-dimension array to track whether the spot is visited
  const visited = Array(m);
  for (let i = 0; i < m; i++) {
    visited[i] = Array(n);
    for (let j = 0; j < n; j++) {
      visited[i][j] = Array(k).fill(false);
    }
  }

  // X is visited
  visited[px[0]][px[1]][0] = true;

  let steps = 0;

  while (queue.length > 0) {
    const p = queue.shift();

    if (p) {
      if (maze[p[0]][p[1]] === 'Y') {
        return steps; // Found Y, return the steps
      }

      // Try 4 directions
      for (let d = 0; d < 4; d++) {
        const x = p[0] + dx[d];
        const y = p[1] + dy[d];
        const z = getLayer(maze, m, n, k, visited, x, y, p[2]);

        if (z >= 0) {
          queue.push([x, y, z]);
          visited[x][y][z] = true;
        }
      }
    } else {
      // One BFS level is finished, step + 1
      steps++;

      if (queue.length > 0) {
        queue.push(null);
      }
    }
  }

  return -1;
};

/**
 * if it's not a wall, same level will be returned
 * if it's a wall can be broken, level + 1
 * otherwise return -1, meaning we cannot go that way
 */
const getLayer = (maze, m, n, k, visited, x, y, z) => {
  if (x < 0 || x >= m || y < 0 || y >= n || visited[x][y][z]) {
    return -1;
  }

  if (maze[x][y] === '#') {
    // if we have broken more than k walls, return -1
    return z < k ? z + 1 : -1;
  }

  return z;
};

export { shortestPath };
<commit_msg>Update shortest-path-in-maze-ii.js<commit_after>/**
 * Shortest Path in Maze II
 *
 * Given a matrix represents a maze, X is the starting point, Y is the destination,
 * # is wall, you can walk in four directions: up, down, left, right.

 * Now you are allowed to break at most k walls, what is the minimum steps to get
 * to Y from X?
 *
 * For example, we have the following maze, and you can break at most 3 walls.
 *
 *     . X . # . .
 *     . # . . # .
 *     . # . . . .
 *     . # # . . .
 *     . Y . . . .
 *     . . . . . #
 *
 * the minimum steps to reach Y from X is 4.
 *
 * Normally we run BFS in a flat level, the idea is to run the BFS in a k-level matrix.
 * Each level is connected thru the wall, there are k levels, each time when we break a wall,
 * we go to the next level, and we can continue to perform BFS from that point. One challenging
 * part is that we need to use a 3-dimensional array to help us check whether a point is visited.
 *
 * Time complexity: O(mnk)
 * note, if k is constant, then time complexity is O(mn)
 */

/**
 * @param {string[][]} maze
 * @param {number[]} px
 * @param {number} k
 */
const shortestPath = (maze, px, k) => {
  const dx = [-1, 1, 0, 0];
  const dy = [0, 0, -1, 1];

  const m = maze.length;
  const n = maze[0].length;

  // Push {x, y, z} to the queue
  // z is 0 by default, represents the layer where no wall is broken
  const queue = [[...px, 0], null];

  // Create a { m x n x k } three-dimension array to track whether the spot is visited
  const visited = Array(m);
  for (let i = 0; i < m; i++) {
    visited[i] = Array(n);
    for (let j = 0; j < n; j++) {
      visited[i][j] = Array(k).fill(false);
    }
  }

  // X is visited
  visited[px[0]][px[1]][0] = true;

  let steps = 0;

  while (queue.length > 0) {
    const p = queue.shift();

    if (p) {
      if (maze[p[0]][p[1]] === 'Y') {
        return steps; // Found Y, return the steps
      }

      // Try 4 directions
      for (let d = 0; d < 4; d++) {
        const x = p[0] + dx[d];
        const y = p[1] + dy[d];
        const z = getLayer(maze, m, n, k, visited, x, y, p[2]);

        if (z >= 0) {
          queue.push([x, y, z]);
          visited[x][y][z] = true;
        }
      }
    } else {
      // One BFS level is finished, step + 1
      steps++;

      if (queue.length > 0) {
        queue.push(null);
      }
    }
  }

  return -1;
};

/**
 * if it's not a wall, same level will be returned
 * if it's a wall can be broken, level + 1
 * otherwise return -1, meaning we cannot go that way
 */
const getLayer = (maze, m, n, k, visited, x, y, z) => {
  if (x < 0 || x >= m || y < 0 || y >= n || visited[x][y][z]) {
    return -1;
  }

  if (maze[x][y] === '#') {
    // if we have broken more than k walls, return -1
    return z < k ? z + 1 : -1;
  }

  return z;
};

export { shortestPath };
<|endoftext|>


<commit_before>/*
 * viewAssignment.js
 * Authors: Zacharias Berggren
 *          Joakim Sandman
 *          Victor Zars
 *
 * Shows the assignment front page and (when the assignment is started)
 * plays the assignment video followed by a countdown and finally records the
 * student answer.
 */

/* Global variable containing some data about the assignment */
var assignmentData = {
    minTime: '',
    maxTime: '',
    assignmentName: '',
    assignmentID: ''
}

/*
 * Shows the assignment front page and sets the assignmentData global variable.
 */
window.AssignmentContent = React.createClass({
    getInitialState: function() {
        return {loaded: false,
                startsAt: '',
                endsAt: '',
                assignmentInformation: ''
                };
    },
    jsonReady: function(data) {
        var json = JSON.parse(data);
        assignmentData.minTime = json["videoIntervall"]["minTimeSeconds"];
        assignmentData.maxTime = json["videoIntervall"]["maxTimeSeconds"];
        assignmentData.assignmentName = json["title"];
        this.setState({loaded: true,
                       startsAt: json["assignmentIntervall"]["startDate"],
                       endsAt: json["assignmentIntervall"]["endDate"],
                       assignmentInformation: json["description"]
                       });
    },
    render: function () {
        assignmentData.assignmentID = this.props.assignment;
        if(!this.state.loaded) {
            getJson("assignments/" + assignmentData.assignmentID, this.jsonReady);
        }
        return (

{assignmentData.assignmentName}

Assignment opens:
{this.state.startsAt}

Assignment closes:
{this.state.endsAt}

Minimum answer video duration:
{assignmentData.minTime} seconds

Maximum answer video duration:
{assignmentData.maxTime} seconds

                        Assignment information:

{this.state.assignmentInformation}

                    <But user={this.props.uid} assignment={this.props
                    .assignment}/>

                    NOTE: Once the assignment starts it cannot be interrupted or paused,

                    remember to test your hardware before you begin!

        )
    }
});

/*
 * Shows the video, question summary, countdown and student recording video
 * in the appropriate order.
 */
var AssignmentStart = React.createClass({
    getInitialState: function() {
        return {startCountDown: false,
                startRecording: false,
                time: 0
                };
    },
    render: function() {
        var questionContent = this.state.startCountDown
                              ? <Question />
                              :
;
        var countDownContent = this.state.startCountDown
                               ? <CountDown record={this.record} />
                               :
;
        var recordContent = this.state.startRecording
                            ?

                                  <StudentRecordVideo autoRecord="true"
                                  studentID={this.props.user}
                                  assignmentID={this.props.assignment} />

                            : <StudentRecordVideo autoRecord="false"
                            studentID={this.props.user} assignmentID={this
                            .props.assignment} />;
        return (

{assignmentData.assignmentName}

Question Video

                                    <Vid count={this.count}/>

                                    {questionContent}

Answer Video

                                    <svg width="30" height="30">
                                        <circle cx="15" cy="15" r="11"
                                        stroke="black" stroke-width="4"
                                        fill="white" id="recCircle" />
                                        Rec circle.
                                    </svg>

[REC]

                                            {countDownContent}

                                        {recordContent}

 Allowed video length: {assignmentData.minTime}-{assignmentData.maxTime}

 Current video length: {this.state.time}

        )
    },
    tick: function() {
        this.setState({time: this.state.time + 1});
    },
    count: function() {
        this.setState({startCountDown: true});
    },
    record: function() {
        var rec = document.getElementById("recCircle");
        rec.style.fill = "red";
        this.setState({startRecording: true});
        this.interval = setInterval(this.tick, 1000);
    }
});

/*
 * Button for starting the assignment. It requires confirmation after clicking.
 */
var But = React.createClass({
    getInitialState: function() {
        return {disabled: false};
    },
    onClick: function() {
        if (confirm("Once the assignment starts it cannot be interrupted or paused.\n" +
                    "Are you sure you want to begin the assignment?")) {
            this.setState({disabled: true});
        }
    },
    render: function() {
        var content = this.state.disabled
                      ? <AssignmentStart user={this.props.user}
                      assignment={this.props.assignment} />
                      : <input
                            disabled = {this.state.disabled}
                            type="submit"
                            id="start-video-button"
                            value="Start Assignment"
                            onClick={this.onClick} />;
        return (
{content}
);
    }
});

/*
 * Shows question summary after retrieving it from the json file.
 */
var Question = React.createClass({
    getInitialState: function() {
        return {question: ''};
    },
    render: function() {
        return (

                Question summary:

                {this.state.question}

        );
    },
    componentDidMount: function() {
        this.serverRequest = getJson("assignments/" + assignmentData.assignmentID, function (data) {
            var json = JSON.parse(data);
            this.setState({question: json["recap"]});
        }.bind(this));
    }
});

/*
 * Shows the countdown and then signal parent to start recording.
 * TODO: Set countdown time to 10 (or read from json).
 */
var CountDown = React.createClass({
    getInitialState: function() {
        return {timeLeft: 3};
    },
    render: function() {
        return
{this.state.timeLeft}
;
    },
    componentDidMount: function() {
        this.interval = setInterval(this.tick, 1000);
    },
    tick: function() {
        this.setState({timeLeft: this.state.timeLeft - 1});
        if(this.state.timeLeft <= 0) {
            this.setState({timeLeft: ''});
            clearInterval(this.interval);
            this.props.record();
        }
    }
});

/*
 * Shows the assignment video (which autoplays), progress bar and time passed.
 * Then signals parent to start countdown and show question summary.
 */
var Vid = React.createClass({
    getInitialState: function() {
        return {currTime: 0,
                totalTime: 0
                };
    },
    render: function() {
        return (

                <video id="videoPlayer" src={"assignments/" + assignmentData.assignmentID + "/video"}>
                    Cannot show video, it may not be supported by your browser!
                </video>

                        <progress id="progress-bar" value={this.state.currTime} max={this.state.totalTime} />

                        {this.state.currTime} / {this.state.totalTime}

        );
    },
    componentDidMount: function() {
        var vid = document.getElementById("videoPlayer");
        //vid.oncontextmenu = function (e) {e.preventDefault();};
        vid.addEventListener("canplay", this.canPlay, false);
        vid.addEventListener("pause", this.onPause, false);
        vid.addEventListener("ended", this.onEnded, false);
        vid.play();
        this.interval = setInterval(this.ticker, 1000);
    },
    ticker: function () {
        var vid = document.getElementById("videoPlayer");
        this.setState({currTime: Math.ceil(vid.currentTime)});
    },
    canPlay: function () {
        var vid = document.getElementById("videoPlayer");
        this.setState({totalTime: Math.ceil(vid.duration)});
    },
    onPause : function () {
        var vid = document.getElementById("videoPlayer");
        vid.play();
    },
    onEnded: function() {
        var vid = document.getElementById("videoPlayer");
        this.setState({currTime: Math.ceil(vid.duration)});
        vid.removeEventListener("pause", this.onPause, false);
        vid.pause();
        clearInterval(this.interval);
        this.props.count();
    }
});

/*
 * Gets json code from the URL and then calls the callback function.
 */
function getJson(URL, callback) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function() {
        if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
            callback(JSON.parse(JSON.stringify(xmlHttp.responseText)));
        }
    }
    xmlHttp.open("GET", URL, true);
    xmlHttp.send();
}

<commit_msg>Small fix to viewAssignment.<commit_after>/*
 * viewAssignment.js
 * Authors: Zacharias Berggren
 *          Joakim Sandman
 *          Victor Zars
 *
 * Shows the assignment front page and (when the assignment is started)
 * plays the assignment video followed by a countdown and finally records the
 * student answer.
 */

/* Global variable containing some data about the assignment */
var assignmentData = {
    minTime: '',
    maxTime: '',
    assignmentName: '',
    assignmentID: ''
}

/*
 * Shows the assignment front page and sets the assignmentData global variable.
 */
window.AssignmentContent = React.createClass({
    getInitialState: function() {
        return {loaded: false,
                startsAt: '',
                endsAt: '',
                assignmentInformation: ''
                };
    },
    jsonReady: function(data) {
        var json = JSON.parse(data);
        assignmentData.minTime = json["videoIntervall"]["minTimeSeconds"];
        assignmentData.maxTime = json["videoIntervall"]["maxTimeSeconds"];
        assignmentData.assignmentName = json["title"];
        this.setState({loaded: true,
                       startsAt: json["assignmentIntervall"]["startDate"],
                       endsAt: json["assignmentIntervall"]["endDate"],
                       assignmentInformation: json["description"]
                       });
    },
    render: function () {
        assignmentData.assignmentID = this.props.assignment;
        if(!this.state.loaded) {
            getJson("assignments/" + assignmentData.assignmentID, this.jsonReady);
        }
        return (

{assignmentData.assignmentName}

Assignment opens:
{this.state.startsAt}

Assignment closes:
{this.state.endsAt}

Minimum answer video duration:
{assignmentData.minTime} seconds

Maximum answer video duration:
{assignmentData.maxTime} seconds

                        Assignment information:

{this.state.assignmentInformation}

                    <But user={this.props.uid} assignment={this.props
                    .assignment}/>

                    NOTE: Once the assignment starts it cannot be interrupted or paused,

                    remember to test your hardware before you begin!

        )
    }
});

/*
 * Shows the video, question summary, countdown and student recording video
 * in the appropriate order.
 */
var AssignmentStart = React.createClass({
    getInitialState: function() {
        return {startCountDown: false,
                startRecording: false,
                time: 0
                };
    },
    render: function() {
        var questionContent = this.state.startCountDown
                              ? <Question />
                              :
;
        var countDownContent = this.state.startCountDown
                               ? <CountDown record={this.record} />
                               :
;
        var recordContent = this.state.startRecording
                            ?

                                  <StudentRecordVideo autoRecord="true"
                                  studentID={this.props.user}
                                  assignmentID={this.props.assignment} />

                            : <StudentRecordVideo autoRecord="false"
                            studentID={this.props.user} assignmentID={this
                            .props.assignment} />;
        return (

{assignmentData.assignmentName}

Question Video

                                    <Vid count={this.count}/>

                                    {questionContent}

Answer Video

                                    <svg width="30" height="30">
                                        <circle cx="15" cy="15" r="11"
                                        stroke="black" stroke-width="4"
                                        fill="white" id="recCircle" />
                                        Rec circle.
                                    </svg>

[REC]

                                            {countDownContent}

                                        {recordContent}

 Allowed video length: {assignmentData.minTime}-{assignmentData.maxTime}

 Current video length: {this.state.time}

        )
    },
    tick: function() {
        this.setState({time: this.state.time + 1});
    },
    count: function() {
        this.setState({startCountDown: true});
    },
    record: function() {
        var rec = document.getElementById("recCircle");
        rec.style.fill = "red";
        this.setState({startRecording: true});
        this.interval = setInterval(this.tick, 1000);
    }
});

/*
 * Button for starting the assignment. It requires confirmation after clicking.
 */
var But = React.createClass({
    getInitialState: function() {
        return {disabled: false};
    },
    onClick: function() {
        if (confirm("Once the assignment starts it cannot be interrupted or paused.\n" +
                    "Are you sure you want to begin the assignment?")) {
            this.setState({disabled: true});
        }
    },
    render: function() {
        var content = this.state.disabled
                      ? <AssignmentStart user={this.props.user}
                      assignment={this.props.assignment} />
                      : <input
                            disabled = {this.state.disabled}
                            type="submit"
                            id="start-video-button"
                            value="Start Assignment"
                            onClick={this.onClick} />;
        return (
{content}
);
    }
});

/*
 * Shows question summary after retrieving it from the json file.
 */
var Question = React.createClass({
    getInitialState: function() {
        return {question: ''};
    },
    render: function() {
        return (

                Question summary:

                {this.state.question}

        );
    },
    componentDidMount: function() {
        this.serverRequest = getJson("assignments/" + assignmentData.assignmentID, function (data) {
            var json = JSON.parse(data);
            this.setState({question: json["recap"]});
        }.bind(this));
    }
});

/*
 * Shows the countdown and then signal parent to start recording.
 * TODO: Set countdown time to 10 (or read from json).
 */
var CountDown = React.createClass({
    getInitialState: function() {
        return {timeLeft: 3};
    },
    render: function() {
        return
{this.state.timeLeft}
;
    },
    componentDidMount: function() {
        this.interval = setInterval(this.tick, 1000);
    },
    tick: function() {
        this.setState({timeLeft: this.state.timeLeft - 1});
        if(this.state.timeLeft <= 0) {
            this.setState({timeLeft: ''});
            clearInterval(this.interval);
            this.props.record();
        }
    }
});

/*
 * Shows the assignment video (which autoplays), progress bar and time passed.
 * Then signals parent to start countdown and show question summary.
 * TODO: fix infinity for short video.
 */
var Vid = React.createClass({
    getInitialState: function() {
        return {currTime: 0,
                totalTime: 0
                };
    },
    render: function() {
        return (

                <video id="videoPlayer" src={"assignments/" + assignmentData.assignmentID + "/video"}>
                    Cannot show video, it may not be supported by your browser!
                </video>

                        <progress id="progress-bar" value={this.state.currTime} max={this.state.totalTime} />

                        {this.state.currTime} / {this.state.totalTime}

        );
    },
    componentDidMount: function() {
        var vid = document.getElementById("videoPlayer");
        vid.oncontextmenu = function (e) {e.preventDefault();};
        vid.addEventListener("canplay", this.canPlay, false);
        vid.addEventListener("pause", this.onPause, false);
        vid.addEventListener("ended", this.onEnded, false);
        vid.play();
        this.interval = setInterval(this.ticker, 1000);
    },
    ticker: function () {
        var vid = document.getElementById("videoPlayer");
        this.setState({currTime: Math.ceil(vid.currentTime)});
    },
    canPlay: function () {
        var vid = document.getElementById("videoPlayer");
        this.setState({totalTime: Math.ceil(vid.duration)});
    },
    onPause : function () {
        var vid = document.getElementById("videoPlayer");
        vid.play();
    },
    onEnded: function() {
        var vid = document.getElementById("videoPlayer");
        this.setState({currTime: Math.ceil(vid.duration)});
        vid.removeEventListener("pause", this.onPause, false);
        vid.pause();
        clearInterval(this.interval);
        this.props.count();
    }
});

/*
 * Gets json code from the URL and then calls the callback function.
 */
function getJson(URL, callback) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function() {
        if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
            callback(JSON.parse(JSON.stringify(xmlHttp.responseText)));
        }
    }
    xmlHttp.open("GET", URL, true);
    xmlHttp.send();
}

<|endoftext|>