<commit_before>'use strict';

var fs = require('fs'),
    path = require('path'),
    http = require('http');

var start_rating = 1000;

var langs = {},
    games = {},
    users = {};

var ts = function(){
  var hrTime = process.hrtime();
  return hrTime[0] * 1000000 + hrTime[1] / 1000;
};

var get_snippet = function(lang, id){
      var filePath = path.normalize('../data/snippets/'+lang+"/"+id);
      return fs.readFileSync(filePath, 'utf8').toString();
};

var get_json = function(name, callback){
  var filePath = path.normalize('../data/'+name+'.json');
  fs.readFile(filePath, 'utf8', function (err, data) {
    if (err) {console.log(err);}
    data = JSON.parse(data);
    callback(data);
  });
};

var save_json = function(name, data, callback){
  var filePath = path.normalize('../data/'+name+'.json');
  fs.writeFile(filePath, JSON.stringify(data),{encoding: "utf8"}, function (err, data) {
    if (err) {console.log(err);}
    callback(data);
  });
};

var shuffle = function shuffle(o){
    for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
    return o;
};

var get_game_set = function(count, ansCount){
	// Get random snippets
	var selectedSnippets = [];
	var elementsSelected = 0;
	var keys = Object.keys(langs);
	var slugs = {};

	while(elementsSelected !== count){
		var selectedLang = keys[Math.floor(Math.random() * keys.length)];
		if(langs[selectedLang].data.length !== 0){
			var tempKeys = Object.keys(langs[selectedLang].data);
			var id = langs[selectedLang].data[tempKeys[Math.floor(Math.random() * tempKeys.length)]];
			var slug = selectedLang + id;
			if(slugs[slug])
				continue;
			slugs[slug] = true;

			// Get ansCount answers, one of them right
			var answers = [selectedLang];
			for(var i = 0; i < ansCount - 1; i++){
				var newAnswer;
				do{
					newAnswer = keys[Math.floor(Math.random() * keys.length)];
				}while(answers.lastIndexOf(newAnswer) !== -1);
				answers.push(newAnswer);
			}

			shuffle(answers);
			selectedSnippets.push( {lang: selectedLang, id: id, answers: answers} );
			elementsSelected++;
		}
	}

	return selectedSnippets;
};

var dt = 100;
var knockdt = 2000;

var server = {
  list_languages: function(params, send){
    var result = [];
    var langcnt = 0, snippetscnt = 0;

    for(var k in langs){
      langcnt ++;
      snippetscnt += langs[k].data.length;

      result.push({name: langs[k].name});
    }
    send({result: 0, languages: result, counts: {languages: langcnt, snippets: snippetscnt}});
  },
  list_users: function(params, send){
    var result = [];
    for (var k in users){
      result.push({id: users[k].id, name: users[k].name, rating: users[k].rating});
    }

    result.sort(function(a, b){
      if(a.rating > b.rating) return -1;
      if(a.rating < b.rating) return 1;
      return 0;
    });

    send({result: 0, users: result});
  },
  list_games: function(params, send){
    var result = [];
    for(var k in games){
      var g = games[k];
      if(g.started){continue;}

      var players = [];
      for (var i in g.players){
        var pid = g.players[i];
        players.push({
          id: users[pid].id,
          name: users[pid].name,
          rating: users[pid].rating
        });
      }

      result.push({
        creator: users[g.creator].name,
        creatorid: users[g.creator].id,
        players: players,
        maxplayers: g.maxplayers,
        name: g.name,
        id: g.id
      });
    }
    send({result: 0, games: result});
  },
  get_snippet: function(params, send){
    if( !params.lang ){send(1000); return;}
    if( !params.id ){send(1001); return;}

    send({
      result: 0,
      snippet: get_snippet( params.lang, params.id)
    });
  },

  wait: function(params, send){
    i--;
    console.log(i);
    var f = function(){
      if (i > 0){setTimeout(function(){f();}, dt);}
      else{ send({result:0}); }
    };
    f();
  },
  wake: function(params, send){
    i=0;
    send({result:0});
  },
  login: function(params, send){
    if( !params.id ){send(1001); return;}
    if( !params.name ){send(1004); return;}
    if( !params.auth ){send(1003); return;}

    // TODO: check that the auth is real :)

    if (!users[params.id]){
      users[params.id] = {
        id: params.id,
        name: params.name,
        auth: params.auth,
        rating: start_rating,
        gameid: 0,
        mana: 0
      };

      save_json('users', users, function(){
        send({result: 0});
      });
      return;
    }else{
      users[params.id].auth = params.auth;
    }

    send({result: 0});
  },
  create_game: function(params, send){
    var id;
    while(true){
      id = Math.random();
      if(id==0 || !games[id]){break;}
    }
    if(!params.players){send(1002); return;}
    if(!params.name){send(1004); return;}
    if(!params.turns){params.turns = 20;}

    if(users[ params.userid ].gameid > 0){
      // TODO: this should check if the game is not started or the game is started and turnuntil < now() else send error
      //games[users[ params.userid ].gameid] = undefined;
    }

    games[id] = {
      id: id,
      creator: params.userid,
      players: [params.userid],
      maxplayers: params.players,
      turn: 0,
      turnuntil: 0,
      knocks: {},
      name: params.name,
      started: false,
      magictime: false,
      questions: [],
      answers: []
    };

    users[ params.userid ].gameid = id;

    var set = get_game_set(params.turns, 4);
    for (var i = 0; i < params.turns ;i++){
      games[id].answers.push([set[i].lang, set[i].answers]);
      games[id].questions.push(get_snippet(set[i].lang, set[i].id));
    }

    send({result:0, game: games[id]});
  },
  join_game: function(params, send){
    // so ... this is the knock - knock thing ...

    if(!params.id){send(1001); return;}
    if(!games[params.gameid]){send(2002); return;}

    var T = ts();
    var game;
    if(users[params.userid].gameid && users[params.userid].gameid !== params.id){
      if(!games[users[params.userid].gameid] ){
        users[params.userid].gameid = 0;
      }else{
        game = games[users[params.userid].gameid];
        if(game.started){
          if(game.turnuntil > T){
            send(2001);
            return;
          }else{
            delete games[users[params.userid].gameid];
          }
        }
      }
    }

    game = games[params.gameid];
    // do we have a user that did not knocked ...

    send({result: 0});
  },
  start_game: function(params, send){

  },
  diff_game: function(params, send){

  },
  options: {
    hostname: 'localhost',
    port: 12346
  },
  errors:{
    1000: "'lang' is required",
    1001: "'id' is required",
    1002: "'players' is required",
    1003: "'auth' is required",
    1004: "'name' is required",

    2000: "Authentication failed. Access denied.",
    2001: "You are already in a game.",
    2002: "Game not found.",
    2003: "The game is already full.",
    2004: "Game already started.",

    4000: "Method not found.",
    5000: "Inernal server error."
  },
  map: {
    "/games": ["list_games", false],
    "/snippet": ["get_snippet", false],
    "/login": ["login", false],
    "/creategame": ["create_game", true],
    "/languages": ["list_languages", false],
    "/users": ["list_users", false],
    "/joingame": ["join_game", true],

  },
  check_auth: function(auth){
    for (var k in users){
      //console.log(users[k].auth == auth);
      if(users[k].auth == auth){
        return users[k].id;
      }
    }
    return false;
  },
  instance: function(request, response){
    var me = {
      send_resp: function(data){
        me.response.writeHeader(200, {
          "Content-Type": "application/json",
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Headers": "GET, POST, OPTIONS, PUT, PATCH, DELETE"
        });
        response.write(JSON.stringify(data));
        response.end();
      },
      send_error: function(code){
        if( !server.errors[code] ){ code=5000; }
        me.send_resp({result:code, message: server.errors[code]});
      },
      init: function(request, response){
        me.request = request;
        me.response = response;

        var parts = request.url.split("?",2);
        var method = parts[0];

        if (parts.length == 1){parts[1] = '';}

        if ( !server.map[method]){
          me.send_error(4000);
          return;
        }
        parts = parts[1].split('&');
        var params={};
        for(var k in parts){
          var sparts = parts[k].split('=');
          if(sparts[0] === ''){continue;}
          if(sparts.length == 1){ sparts[1] = ''; }
          sparts[1] = sparts[1].split("+").join(" ");
          params[sparts[0]] = decodeURIComponent(sparts[1]);
        }

        var mapped = server.map[method];
        if(mapped[1]){
          if(!params.auth){
            me.send_error(2000);
            return;
          }
          params.userid = server.check_auth(params.auth);
          if(!params.userid){
            me.send_error(2000);
            return;
          }
        }

        server[mapped[0]] (params, function(result){
          if(!result || typeof result == "number" ){
            me.send_error(result);
          }else{
            me.send_resp(result);
          }
        });
      }
    };
    me.init(request, response);
  }
};

var init = function(){
  get_json('langs', function(ret){
    for (var k in ret){
      langs[k] = {name: ret[k], data: []};
    }
    get_json('snippets', function(ret){
      for (var k in ret){
        langs[k].data = ret[k];
      }
      get_json('users', function(ret){
        users = ret;
        get_json('games', function(ret){
          games = ret;

          http.createServer(function(request,response){
            console.log(request.url);
            server.instance(request, response);
          }).listen(server.options.port, server.options.host);
        });
      });
    });
  });
};

init();
<commit_msg>Improvements of the server<commit_after>'use strict';

var fs = require('fs'),
    path = require('path'),
    http = require('http');

var start_rating = 1000;

var langs = {},
    games = {},
    users = {};

var ts = function(){
  var hrTime = process.hrtime();
  return hrTime[0] * 1000000 + hrTime[1] / 1000;
};

var get_snippet = function(lang, id){
      var filePath = path.normalize('../data/snippets/'+lang+"/"+id);
      return fs.readFileSync(filePath, 'utf8').toString();
};

var get_json = function(name, callback){
  var filePath = path.normalize('../data/'+name+'.json');
  fs.readFile(filePath, 'utf8', function (err, data) {
    if (err) {console.log(err);}
    data = JSON.parse(data);
    callback(data);
  });
};

var save_json = function(name, data, callback){
  var filePath = path.normalize('../data/'+name+'.json');
  fs.writeFile(filePath, JSON.stringify(data),{encoding: "utf8"}, function (err, data) {
    if (err) {console.log(err);}
    callback(data);
  });
};

var shuffle = function shuffle(o){
    for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
    return o;
};

var get_game_set = function(count, ansCount){
	// Get random snippets
	var selectedSnippets = [];
	var elementsSelected = 0;
	var keys = Object.keys(langs);
	var slugs = {};

	while(elementsSelected !== count){
		var selectedLang = keys[Math.floor(Math.random() * keys.length)];
		if(langs[selectedLang].data.length !== 0){
			var tempKeys = Object.keys(langs[selectedLang].data);
			var id = langs[selectedLang].data[tempKeys[Math.floor(Math.random() * tempKeys.length)]];
			var slug = selectedLang + id;
			if(slugs[slug])
				continue;
			slugs[slug] = true;

			// Get ansCount answers, one of them right
			var answers = [selectedLang];
			for(var i = 0; i < ansCount - 1; i++){
				var newAnswer;
				do{
					newAnswer = keys[Math.floor(Math.random() * keys.length)];
				}while(answers.lastIndexOf(newAnswer) !== -1);
				answers.push(newAnswer);
			}

			shuffle(answers);
			selectedSnippets.push( {lang: selectedLang, id: id, answers: answers} );
			elementsSelected++;
		}
	}

	return selectedSnippets;
};

var get_game_current_question = function(gameid){
  if(!games[gameid]){return false;}
  var game = games[gameid];
  if(!game){return false;}
  if(game.turn >= games[gameid].questions.length){ return false; }

  var data = {
    question: game.questions[game.turn],
    answers: game.answers[game.turn][1]
  };
};

var get_game_current_answer = function(gameid){
  if(!games[gameid]){return false;}
  var game = games[gameid];
  if(!game){return false;}
  if(game.turn >= games[gameid].questions.length){ return false; }

  return game.answers[game.turn][0];
};

var get_userdatas = function(userids){
  var result = [];
  for (var k in userids){
    result.push({id: users[k].id, name: users[k].name, rating: users[k].rating});
  }

  result.sort(function(a, b){
    if(a.rating > b.rating) return -1;
    if(a.rating < b.rating) return 1;
    return 0;
  });

  return result;
};

var dt = 100;

var knockdt = 3000000;

var server = {
  list_languages: function(params, send){
    var result = [];
    var langcnt = 0, snippetscnt = 0;

    for(var k in langs){
      langcnt ++;
      snippetscnt += langs[k].data.length;

      result.push({name: langs[k].name});
    }
    send({result: 0, languages: result, counts: {languages: langcnt, snippets: snippetscnt}});
  },
  list_users: function(params, send){
    var result = get_userdatas(Object.keys(users));
    send({result: 0, users:result});
  },
  list_games: function(params, send){
    var result = [];
    for(var k in games){
      var g = games[k];
      if(g.started){continue;}

      var players = [];
      for (var i in g.players){
        var pid = g.players[i];
        players.push({
          id: users[pid].id,
          name: users[pid].name,
          rating: users[pid].rating
        });
      }

      result.push({
        creator: users[g.creator].name,
        creatorid: users[g.creator].id,
        players: players,
        maxplayers: g.maxplayers,
        name: g.name,
        id: g.id
      });
    }
    send({result: 0, games: result});
  },
  get_snippet: function(params, send){
    if( !params.lang ){send(1000); return;}
    if( !params.id ){send(1001); return;}

    send({
      result: 0,
      snippet: get_snippet( params.lang, params.id)
    });
  },

  wait: function(params, send){
    i--;
    console.log(i);
    var f = function(){
      if (i > 0){setTimeout(function(){f();}, dt);}
      else{ send({result:0}); }
    };
    f();
  },
  wake: function(params, send){
    i=0;
    send({result:0});
  },
  login: function(params, send){
    if( !params.id ){send(1001); return;}
    if( !params.name ){send(1004); return;}
    if( !params.auth ){send(1003); return;}

    // TODO: check that the auth is real :)

    if (!users[params.id]){
      users[params.id] = {
        id: params.id,
        name: params.name,
        auth: params.auth,
        rating: start_rating,
        gameid: 0,
        mana: 0
      };

      save_json('users', users, function(){
        send({result: 0});
      });
      return;
    }else{
      users[params.id].auth = params.auth;
    }

    send({result: 0});
  },
  create_game: function(params, send){
    var id;
    while(true){
      id = Math.random();
      if(id==0 || !games[id]){break;}
    }
    if(!params.players){send(1002); return;}
    if(!params.name){send(1004); return;}
    if(!params.turns){params.turns = 20;}

    if(users[ params.userid ].gameid > 0){
      // TODO: this should check if the game is not started or the game is started and turnuntil < now() else send error
      //games[users[ params.userid ].gameid] = undefined;
    }

    games[id] = {
      id: id,
      creator: params.userid,
      players: [params.userid],
      maxplayers: params.players,
      turn: 0,
      turnuntil: 0,
      knocks: {},
      name: params.name,
      started: false,
      magictime: false,
      questions: [],
      answers: []
    };

    users[ params.userid ].gameid = id;

    var set = get_game_set(params.turns, 4);
    for (var i = 0; i < params.turns ;i++){
      games[id].answers.push([set[i].lang, set[i].answers]);
      games[id].questions.push(get_snippet(set[i].lang, set[i].id));
    }

    send({result:0, game: id});
  },
  join_game: function(params, send){
    // so ... this is the knock - knock thing ...

    if(!params.id){send(1001); return;}
    if(!games[params.id]){send(2002); return;}

    var T = ts();
    var game;
    if(users[params.userid].gameid && users[params.userid].gameid !== params.id){
      if(!games[users[params.userid].gameid] ){
        users[params.userid].gameid = 0;
      }else{
        game = games[users[params.userid].gameid];
        if(game.started){
          if(game.turnuntil > T){
            send(2001);
            return;
          }else{
            delete games[users[params.userid].gameid];
          }
        }
      }
    }

    if( games[[params.id]].started){
      send(2004);
      return;
    }

    game = games[params.id];

    if(!game.knocks){
      send(2007);
      return;
    }

    var cnt = 0, hostleft = false, knocked  = [], meknocked = false;
    for(var k in game.players){
      var playerid = game.players[k];
      if( game.knocks[playerid] ){
        if(playerid === params.userid){meknocked = true;}
        if(game.knocks[playerid] < T - knockdt){
          users[playerid].gameid = 0;
          if(playerid === game.creator){
            hostleft = true;
          }
          if(playerid === params.userid){
            send(2006);
            return;
          }
        }else{
          cnt ++;
          knocked.push(game.players[k]);
        }
      }
    }
    if( !meknocked ){
      knocked.push(params.userid);
      cnt ++;
    }

    if(cnt > game.maxplayers){
      send(2003);
      return;
    }

    if(hostleft){
      delete games[params.id];
      send_error(2005);
      return;
    }

    games[params.id].players = knocked;
    games[params.id].knocks[ params.userid ] = T;

    var players = get_userdatas(knocked);
    var question = get_game_current_question(game.id);

    send({result: 0, started: game.started, players: players, question: question});
  },
  start_game: function(params, send){
    if(!params.id){send(1001); return;}
    if(!games[params.id]){send(2002); return;}
    if(!games[params.id]){send(2002); return;}

  },
  diff_game: function(params, send){

  },
  options: {
    hostname: 'localhost',
    port: 12346
  },
  errors:{
    1000: "'lang' is required",
    1001: "'id' is required",
    1002: "'players' is required",
    1003: "'auth' is required",
    1004: "'name' is required",

    2000: "Authentication failed. Access denied.",
    2001: "You are already in a game.",
    2002: "Game not found.",
    2003: "The game is already full.",
    2004: "Game already started.",
    2005: "The host left the game.",
    2006: "You have too slow connection for this game.",
    2007: "This is a demo game.",

    4000: "Method not found.",
    5000: "Inernal server error."
  },
  map: {
    "/games": ["list_games", false],
    "/snippet": ["get_snippet", false],
    "/login": ["login", false],
    "/creategame": ["create_game", true],
    "/languages": ["list_languages", false],
    "/users": ["list_users", false],
    "/joingame": ["join_game", true],

  },
  check_auth: function(auth){
    for (var k in users){
      //console.log(users[k].auth == auth);
      if(users[k].auth == auth){
        if( k != users[k].id) {return false;}
        return users[k].id;
      }
    }
    return false;
  },
  instance: function(request, response){
    var me = {
      send_resp: function(data){
        me.response.writeHeader(200, {
          "Content-Type": "application/json",
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Headers": "GET, POST, OPTIONS, PUT, PATCH, DELETE"
        });
        response.write(JSON.stringify(data));
        response.end();
      },
      send_error: function(code){
        if( !server.errors[code] ){ code=5000; }
        me.send_resp({result:code, message: server.errors[code]});
      },
      init: function(request, response){
        me.request = request;
        me.response = response;

        var parts = request.url.split("?",2);
        var method = parts[0];

        if (parts.length == 1){parts[1] = '';}

        if ( !server.map[method]){
          me.send_error(4000);
          return;
        }
        parts = parts[1].split('&');
        var params={};
        for(var k in parts){
          var sparts = parts[k].split('=');
          if(sparts[0] === ''){continue;}
          if(sparts.length == 1){ sparts[1] = ''; }
          sparts[1] = sparts[1].split("+").join(" ");
          params[sparts[0]] = decodeURIComponent(sparts[1]);
        }

        var mapped = server.map[method];
        if(mapped[1]){
          if(!params.auth){
            me.send_error(2000);
            return;
          }
          params.userid = server.check_auth(params.auth);
          if(!params.userid){
            me.send_error(2000);
            return;
          }
        }

        server[mapped[0]] (params, function(result){
          if(!result || typeof result == "number" ){
            me.send_error(result);
          }else{
            me.send_resp(result);
          }
        });
      }
    };
    me.init(request, response);
  }
};

var init = function(){
  get_json('langs', function(ret){
    for (var k in ret){
      langs[k] = {name: ret[k], data: []};
    }
    get_json('snippets', function(ret){
      for (var k in ret){
        langs[k].data = ret[k];
      }
      get_json('users', function(ret){
        users = ret;
        get_json('games', function(ret){
          games = ret;

          http.createServer(function(request,response){
            console.log(request.url);
            server.instance(request, response);
          }).listen(server.options.port, server.options.host);
        });
      });
    });
  });
};

init();
<|endoftext|>


<commit_before>import {safeLoad} from 'js-yaml';
import Promise from 'bluebird';
var fs = Promise.promisifyAll(require('fs'));

export async function loadMenuTree(versionName) {
  return await loadYML(`${versionName}.yml`);
}

export function getPath(obj, path) {
  if (obj) {
    if (path && path.length>0) {
      var [head,...tail] = path;
      var child = obj[head];
      return getPath(child, tail);
    } else {
      return obj;
    }
  }
}

async function loadYML(fileName) {
  return safeLoad(await fs.readFileAsync('./content/' + fileName, 'utf8'));
}
<commit_msg>loadMenuTree diretory prefix bug<commit_after>import {safeLoad} from 'js-yaml';
import Promise from 'bluebird';
var fs = Promise.promisifyAll(require('fs'));

export async function loadMenuTree(versionName) {
  return await loadYML(`${versionName}.yml`);
}

export function getPath(obj, path) {
  if (obj) {
    if (path && path.length>0) {
      var [head,...tail] = path;
      var child = obj[head];
      return getPath(child, tail);
    } else {
      return obj;
    }
  }
}

async function loadYML(fileName) {
  return safeLoad(await fs.readFileAsync('../content/' + fileName, 'utf8'));
}
<|endoftext|>


<commit_before>'use strict';

/* global describe, beforeEach, it, expect */
/* global element, by, browser */

describe('pattyApp', function() {

  beforeEach(function() {
    browser.get('index.html');
  });

  it('should have patty title', function() {
    expect(browser.getTitle()).toMatch('Project Patty Visualisation');
  });

  describe('initial state', function() {
    it('should have zero search results', function() {
      expect(element.all(by.css('.search-result')).count()).toBe(0);
    });
    it('should not show settings panel', function() {
      var panel = element(by.css('.settings-panel'));
      expect(panel.isDisplayed()).toBeFalsy();
    });

    it('should not show tools', function() {
      var panel = element(by.css('.toolbox-tools'));
      expect(panel.isDisplayed()).toBeFalsy();
    });
  });

  describe('searched on "site:162"', function() {
    beforeEach(function() {
      element(by.model('sp.query')).sendKeys('site:162');
    });

    it('should have one site details div', function() {
      expect(element.all(by.css('.site-details')).count()).toBe(1);
    });
  });

  describe('search on "zzzz"', function() {
    beforeEach(function() {
      element(by.model('sp.query')).sendKeys('zzzz');
    });

    it('should have zero search results', function() {
      expect(element.all(by.css('.search-result')).count()).toBe(0);
    });
  });

  describe('click on settings gear', function() {
    beforeEach(function() {
      element(by.css('.icon-big.gear-icon')).click();
    });

    it('should show settings panel', function() {
      var panel = element(by.css('.settings-panel'));
      expect(panel.isDisplayed()).toBeTruthy();
    });
  });

  describe('click on help icon', function() {
    beforeEach(function() {
      element(by.css('.icon-big.help-icon')).click();
    });

    it('should show help panel', function() {
      var panel = element(by.css('.help-panel'));
      expect(panel.isDisplayed()).toBeTruthy();
    });
  });

  describe('clicking on toolbox icon', function() {
    beforeEach(function() {
      element(by.css('.icon-big.toolbox-icon')).click();
    });

    it('should show tools', function() {
      var panel = element(by.css('.toolbox-tools'));
      expect(panel.isDisplayed()).toBeTruthy();
    });

    describe('and then clicking on bottom toolbox icon', function() {

      beforeEach(function() {
        element(by.css('.icon-big.toolbox-icon')).click();
        // wait for toolbox to close, otherwise it will still be displayed partly
        browser.sleep(200);
      });

      it('should hide tools', function() {
        var panel = element(by.css('.toolbox-tools'));
        expect(panel.isDisplayed()).toBeFalsy();
      });
    });

    describe('and then clicking on top toolbox icon', function() {

      beforeEach(function() {
        element(by.css('.icon-big.toolbox-tray-top-icon')).click();
        // wait for toolbox to close, otherwise it will still be displayed partly
        browser.sleep(500);
      });

      it('should hide tools', function() {
        var panel = element(by.css('.toolbox-tools'));
        expect(panel.isDisplayed()).toBeFalsy();
      });
    });
  });

});
<commit_msg>During e2e test the click on top tray icon failed a lot of the time, added sleep so click is successfull.<commit_after>'use strict';

/* global describe, beforeEach, it, expect */
/* global element, by, browser */

describe('pattyApp', function() {

  beforeEach(function() {
    browser.get('index.html');
  });

  it('should have patty title', function() {
    expect(browser.getTitle()).toMatch('Project Patty Visualisation');
  });

  describe('initial state', function() {
    it('should have zero search results', function() {
      expect(element.all(by.css('.search-result')).count()).toBe(0);
    });
    it('should not show settings panel', function() {
      var panel = element(by.css('.settings-panel'));
      expect(panel.isDisplayed()).toBeFalsy();
    });

    it('should not show tools', function() {
      var panel = element(by.css('.toolbox-tools'));
      expect(panel.isDisplayed()).toBeFalsy();
    });
  });

  describe('searched on "site:162"', function() {
    beforeEach(function() {
      element(by.model('sp.SitesService.query')).sendKeys('site:162');
    });

    it('should have one site details div', function() {
      expect(element.all(by.css('.site-details')).count()).toBe(1);
    });
  });

  describe('search on "zzzz"', function() {
    beforeEach(function() {
      element(by.model('sp.SitesService.query')).sendKeys('zzzz');
    });

    it('should have zero search results', function() {
      expect(element.all(by.css('.search-result')).count()).toBe(0);
    });
  });

  describe('click on settings gear', function() {
    beforeEach(function() {
      element(by.css('.icon-big.gear-icon')).click();
    });

    it('should show settings panel', function() {
      var panel = element(by.css('.settings-panel'));
      expect(panel.isDisplayed()).toBeTruthy();
    });
  });

  describe('click on help icon', function() {
    beforeEach(function() {
      element(by.css('.icon-big.help-icon')).click();
    });

    it('should show help panel', function() {
      var panel = element(by.css('.help-panel'));
      expect(panel.isDisplayed()).toBeTruthy();
    });
  });

  describe('clicking on toolbox icon', function() {
    beforeEach(function() {
      element(by.css('.icon-big.toolbox-icon')).click();
    });

    it('should show tools', function() {
      var panel = element(by.css('.toolbox-tools'));
      expect(panel.isDisplayed()).toBeTruthy();
    });

    describe('and then clicking on bottom toolbox icon', function() {

      beforeEach(function() {
        element(by.css('.icon-big.toolbox-icon')).click();
        // wait for toolbox to close, otherwise it will still be displayed partly
        browser.sleep(200);
      });

      it('should hide tools', function() {
        var panel = element(by.css('.toolbox-tools'));
        expect(panel.isDisplayed()).toBeFalsy();
      });
    });

    describe('and then clicking on top toolbox icon', function() {

      beforeEach(function() {
        // wait for toolbox to open, otherwise icon is moving when click is attempted
        browser.sleep(500);
        element(by.css('.icon-big.toolbox-tray-top-icon')).click();
        // wait for toolbox to close, otherwise it will still be displayed partly
        browser.sleep(200);
      });

      it('should hide tools', function() {
        var panel = element(by.css('.toolbox-tools'));
        expect(panel.isDisplayed()).toBeFalsy();
      });
    });
  });

});
<|endoftext|>


<commit_before>const fs = require('fs')
const path = require('path')
const Promise = require('bluebird')
const archiver = require('archiver')
const uuid = require('uuid')
const fallback = require('./fallback.js')
const responseXlsx = require('./responseXlsx.js')
const jsonToXml = require('./jsonToXml.js')
const stream = require('stream')
const zlib = require('zlib')
const merge = require('merge2')

const stringToStream = (str) => {
  var s = new stream.Readable()
  s.push(str)
  s.push(null)
  return s
}

module.exports = (reporter, req, res) => {
  reporter.logger.debug('Parsing xlsx content', req)
  const contentString = res.content.toString()
  let $xlsxTemplate
  let $files
  try {
    let content = JSON.parse(contentString)
    $xlsxTemplate = content.$xlsxTemplate
    $files = content.$files
  } catch (e) {
    reporter.logger.warn('Unable to parse xlsx template JSON string (maybe you are missing {{{xlsxPrint}}} at the end?): \n' + contentString.substring(0, 100) + '... \n' + e.stack, req)
    return fallback(e, req, res)
  }

  const files = Object.keys($xlsxTemplate).map((k) => {
    if (k.includes('xl/media/') || k.includes('.bin')) {
      return {
        path: k,
        data: Buffer.from($xlsxTemplate[k], 'base64')
      }
    }

    if (k.includes('.xml')) {
      const xmlAndFiles = jsonToXml($xlsxTemplate[k])
      const fullXml = xmlAndFiles.xml

      if (fullXml.indexOf('&&') < 0) {
        return {
          path: k,
          data: Buffer.from(fullXml, 'utf8')
        }
      }

      const xmlStream = merge()

      if (fullXml.indexOf('&&') < 0) {
        return {
          path: k,
          data: Buffer.from(fullXml, 'utf8')
        }
      }

      let xml = fullXml

      while (xml) {
        const separatorIndex = xml.indexOf('&&')

        if (separatorIndex < 0) {
          xmlStream.add(stringToStream(xml))
          xml = ''
          continue
        }

        xmlStream.add(stringToStream(xml.substring(0, separatorIndex)))
        xmlStream.add(fs.createReadStream($files[xmlAndFiles.files.shift()]).pipe(zlib.createInflate()))
        xml = xml.substring(separatorIndex + '&&'.length)
      }

      return {
        path: k,
        data: xmlStream
      }
    }

    return {
      path: k,
      data: Buffer.from($xlsxTemplate[k], 'utf8')
    }
  })

  return new Promise((resolve, reject) => {
    const id = uuid.v1()
    const xlsxFileName = path.join(reporter.options.tempDirectory, id + '.xlsx')
    reporter.logger.debug('Zipping prepared xml files into ' + xlsxFileName, req)
    const archive = archiver('zip')
    const output = fs.createWriteStream(xlsxFileName)

    output.on('close', () => {
      reporter.logger.debug('Successfully zipped now.', reporter.logger)
      res.stream = fs.createReadStream(xlsxFileName)
      responseXlsx(reporter, req, res).then(() => resolve()).catch((e) => reject(e))
    })

    archive.on('error', (err) => reject(err))

    archive.pipe(output)
    files.forEach((f) => archive.append(f.data, { name: f.path }))
    archive.finalize()
  })
}
<commit_msg>use tempAutoCleanupDirectory<commit_after>const fs = require('fs')
const path = require('path')
const Promise = require('bluebird')
const archiver = require('archiver')
const uuid = require('uuid')
const fallback = require('./fallback.js')
const responseXlsx = require('./responseXlsx.js')
const jsonToXml = require('./jsonToXml.js')
const stream = require('stream')
const zlib = require('zlib')
const merge = require('merge2')

const stringToStream = (str) => {
  var s = new stream.Readable()
  s.push(str)
  s.push(null)
  return s
}

module.exports = (reporter, req, res) => {
  reporter.logger.debug('Parsing xlsx content', req)
  const contentString = res.content.toString()
  let $xlsxTemplate
  let $files
  try {
    let content = JSON.parse(contentString)
    $xlsxTemplate = content.$xlsxTemplate
    $files = content.$files
  } catch (e) {
    reporter.logger.warn('Unable to parse xlsx template JSON string (maybe you are missing {{{xlsxPrint}}} at the end?): \n' + contentString.substring(0, 100) + '... \n' + e.stack, req)
    return fallback(e, req, res)
  }

  const files = Object.keys($xlsxTemplate).map((k) => {
    if (k.includes('xl/media/') || k.includes('.bin')) {
      return {
        path: k,
        data: Buffer.from($xlsxTemplate[k], 'base64')
      }
    }

    if (k.includes('.xml')) {
      const xmlAndFiles = jsonToXml($xlsxTemplate[k])
      const fullXml = xmlAndFiles.xml

      if (fullXml.indexOf('&&') < 0) {
        return {
          path: k,
          data: Buffer.from(fullXml, 'utf8')
        }
      }

      const xmlStream = merge()

      if (fullXml.indexOf('&&') < 0) {
        return {
          path: k,
          data: Buffer.from(fullXml, 'utf8')
        }
      }

      let xml = fullXml

      while (xml) {
        const separatorIndex = xml.indexOf('&&')

        if (separatorIndex < 0) {
          xmlStream.add(stringToStream(xml))
          xml = ''
          continue
        }

        xmlStream.add(stringToStream(xml.substring(0, separatorIndex)))
        xmlStream.add(fs.createReadStream($files[xmlAndFiles.files.shift()]).pipe(zlib.createInflate()))
        xml = xml.substring(separatorIndex + '&&'.length)
      }

      return {
        path: k,
        data: xmlStream
      }
    }

    return {
      path: k,
      data: Buffer.from($xlsxTemplate[k], 'utf8')
    }
  })

  return new Promise((resolve, reject) => {
    const id = uuid.v1()
    const xlsxFileName = path.join(reporter.options.tempAutoCleanupDirectory , id + '.xlsx')
    reporter.logger.debug('Zipping prepared xml files into ' + xlsxFileName, req)
    const archive = archiver('zip')
    const output = fs.createWriteStream(xlsxFileName)

    output.on('close', () => {
      reporter.logger.debug('Successfully zipped now.', reporter.logger)
      res.stream = fs.createReadStream(xlsxFileName)
      responseXlsx(reporter, req, res).then(() => resolve()).catch((e) => reject(e))
    })

    archive.on('error', (err) => reject(err))

    archive.pipe(output)
    files.forEach((f) => archive.append(f.data, { name: f.path }))
    archive.finalize()
  })
}
<|endoftext|>


<commit_before>/* eslint-disable no-unused-expressions */
import { remote } from 'electron';
import React from 'react';
import chai, { expect } from 'chai';
import { mount } from 'enzyme';
import sinon from 'sinon';

import PlayerPage from '../../../../build/renderer/ui/pages/PlayerPage';
import mockSettings, { getVars } from '../_mockSettings';

chai.should();

describe('<PlayerPage />', () => {
  let fired;

  beforeEach(() => {
    mockSettings();
    fired = getVars().fired;
  });

  it('should render a WindowContainer root', () => {
    const component = mount(<PlayerPage />);
    component.find('WindowContainer').length.should.be.equal(1);
  });

  it('should initialy be in a non-ready state', () => {
    const component = mount(<PlayerPage />);
    component.instance().ready.should.be.equal(false);
  });

  it('should show the tray modal when closing the window', () => {
    const component = mount(<PlayerPage />);
    component.find('WindowContainer').props().confirmClose();
    // TODO: Test this somehow
  });

  it('shouldbe marked as ready when the DOM is ready inside the webview', (done) => {
    const component = mount(<PlayerPage />);
    const stub = sinon.stub(window, 'addEventListener');
    const _on = remote.getCurrentWindow().on;
    const windowSpy = remote.getCurrentWindow().on = sinon.spy();
    component.instance()._domReady();
    component.instance().refs.view = {
      focus: sinon.spy(),
    };
    setTimeout(() => {
      component.instance().ready.should.be.equal(true);
      component.instance().refs.view.focus.callCount.should.be.equal(1);
      stub.callCount.should.be.eq(17);
      stub.getCall(16).args[1]();
      windowSpy.firstCall.args[0].should.be.equal('focus');
      const queryStub = sinon.stub(document, 'querySelector');
      const focusObj = {
        focus: sinon.spy(),
      };
      queryStub.returns(focusObj);
      windowSpy.firstCall.args[1]();
      queryStub.callCount.should.be.equal(1);
      focusObj.focus.callCount.should.be.equal(1);
      queryStub.restore();
      stub.restore();
      remote.getCurrentWindow().on = _on;
      done();
    }, 800);
  });

  it('should immediately navigate to the target page once when the webview stops loading', (done) => {
    const component = mount(<PlayerPage />);
    component.instance().once.should.be.equal(true);
    document.body.setAttribute('loading', 'loading');
    component.instance()._didStopLoading();
    component.instance().once.should.be.equal(false);
    setTimeout(() => {
      expect(document.body.getAttribute('loading')).to.be.equal(null);
      component.instance()._didStopLoading();
      component.instance().once.should.be.equal(false);
      done();
    }, 400);
  });

  it('should persist GPM URLS on navigate events', () => {
    const component = mount(<PlayerPage />);
    component.instance().ready = true;
    component.instance()._didNavigate('https://play.google.com/music/now');
    fired['settings:set'].should.be.ok;
  });

  it('should persist GPM URLS on navigate in pageevents', () => {
    const component = mount(<PlayerPage />);
    component.instance().ready = true;
    component.instance()._didNavigateInPage('https://play.google.com/music/now');
    fired['settings:set'].should.be.ok;
  });

  it('should not persist GPM URLS on navigate events when not ready', () => {
    const component = mount(<PlayerPage />);
    component.instance()._didNavigate('https://play.google.com/music/now');
    expect(fired['settings:set']).to.not.be.ok;
  });

  it('should not persist GPM URLS on navigate in page events when not ready', () => {
    const component = mount(<PlayerPage />);
    component.instance()._didNavigateInPage('https://play.google.com/music/now');
    expect(fired['settings:set']).to.not.be.ok;
  });

  it('should not persist non-GPM URLS on navigate events', () => {
    const component = mount(<PlayerPage />);
    component.instance().ready = true;
    component.instance()._didNavigate('https://www.google.com');
    expect(fired['settings:set']).to.not.be.ok;
  });

  it('should not persist non-GPM URLS on navigate in page events', () => {
    const component = mount(<PlayerPage />);
    component.instance().ready = true;
    component.instance()._didNavigateInPage('https://www.google.com');
    expect(fired['settings:set']).to.not.be.ok;
  });
});
<commit_msg>Fix tests<commit_after>/* eslint-disable no-unused-expressions */
import { remote } from 'electron';
import React from 'react';
import chai, { expect } from 'chai';
import { mount } from 'enzyme';
import sinon from 'sinon';

import PlayerPage from '../../../../build/renderer/ui/pages/PlayerPage';
import mockSettings, { getVars } from '../_mockSettings';

chai.should();

describe('<PlayerPage />', () => {
  let fired;

  beforeEach(() => {
    mockSettings();
    fired = getVars().fired;
  });

  it('should render a WindowContainer root', () => {
    const component = mount(<PlayerPage />);
    component.find('WindowContainer').length.should.be.equal(1);
  });

  it('should initialy be in a non-ready state', () => {
    const component = mount(<PlayerPage />);
    component.instance().ready.should.be.equal(false);
  });

  it('should show the tray modal when closing the window', () => {
    const component = mount(<PlayerPage />);
    component.find('WindowContainer').props().confirmClose();
    // TODO: Test this somehow
  });

  it('shouldbe marked as ready when the DOM is ready inside the webview', (done) => {
    const component = mount(<PlayerPage />);
    const stub = sinon.stub(window, 'addEventListener');
    const _on = remote.getCurrentWindow().on;
    const windowSpy = remote.getCurrentWindow().on = sinon.spy();
    component.instance()._domReady();
    component.instance().refs.view = {
      focus: sinon.spy(),
    };
    setTimeout(() => {
      component.instance().ready.should.be.equal(true);
      component.instance().refs.view.focus.callCount.should.be.equal(1);
      stub.callCount.should.be.eq(19);
      stub.getCall(16).args[1]({});
      windowSpy.firstCall.args[0].should.be.equal('focus');
      const queryStub = sinon.stub(document, 'querySelector');
      const focusObj = {
        focus: sinon.spy(),
      };
      queryStub.returns(focusObj);
      windowSpy.firstCall.args[1]();
      queryStub.callCount.should.be.equal(1);
      focusObj.focus.callCount.should.be.equal(1);
      queryStub.restore();
      stub.restore();
      remote.getCurrentWindow().on = _on;
      done();
    }, 800);
  });

  it('should immediately navigate to the target page once when the webview stops loading', (done) => {
    const component = mount(<PlayerPage />);
    component.instance().once.should.be.equal(true);
    document.body.setAttribute('loading', 'loading');
    component.instance()._didStopLoading();
    component.instance().once.should.be.equal(false);
    setTimeout(() => {
      expect(document.body.getAttribute('loading')).to.be.equal(null);
      component.instance()._didStopLoading();
      component.instance().once.should.be.equal(false);
      done();
    }, 400);
  });

  it('should persist GPM URLS on navigate events', () => {
    const component = mount(<PlayerPage />);
    component.instance().ready = true;
    component.instance()._didNavigate('https://play.google.com/music/now');
    fired['settings:set'].should.be.ok;
  });

  it('should persist GPM URLS on navigate in pageevents', () => {
    const component = mount(<PlayerPage />);
    component.instance().ready = true;
    component.instance()._didNavigateInPage('https://play.google.com/music/now');
    fired['settings:set'].should.be.ok;
  });

  it('should not persist GPM URLS on navigate events when not ready', () => {
    const component = mount(<PlayerPage />);
    component.instance()._didNavigate('https://play.google.com/music/now');
    expect(fired['settings:set']).to.not.be.ok;
  });

  it('should not persist GPM URLS on navigate in page events when not ready', () => {
    const component = mount(<PlayerPage />);
    component.instance()._didNavigateInPage('https://play.google.com/music/now');
    expect(fired['settings:set']).to.not.be.ok;
  });

  it('should not persist non-GPM URLS on navigate events', () => {
    const component = mount(<PlayerPage />);
    component.instance().ready = true;
    component.instance()._didNavigate('https://www.google.com');
    expect(fired['settings:set']).to.not.be.ok;
  });

  it('should not persist non-GPM URLS on navigate in page events', () => {
    const component = mount(<PlayerPage />);
    component.instance().ready = true;
    component.instance()._didNavigateInPage('https://www.google.com');
    expect(fired['settings:set']).to.not.be.ok;
  });
});
<|endoftext|>


<commit_before>var db = require('../schema');
var Timer = require('./timer');
var _ = require('lodash');

var Users = require('../collections/users');

var Room = db.Model.extend({
  tableName: 'Rooms',
  hasTimestamps: true,

  initialize: function() {
    this.users = new Users(); //bookshelf Users collection
    this.djQueue = []; //array of bookshelf User models
    this.currentMedia = null; //bookshelf Media model
    this.mediaTimeElapsed = 0;  //seconds elapsed on currentMedia
    this.mediaTimer = null;  //Timer object
    this.sockets = null;
  },

  setSocket: function(socket) {
    this.sockets = socket;
  },

  playMedia: function() {
    if (this.mediaTimer) {
      this.mediaTimer.stop();
      this.mediaTimer = null;
    }

    if (this.djQueue[0] && this.djQueue[0].getCurrentPlaylist()) {
      var dj = this.djQueue[0];
      var playlist = dj.getCurrentPlaylist();
      this.currentMedia = playlist.getCurrentMedia();
      this.currentMedia.incrementPlayCount()
      .then(function(data) {
        console.log('incrementing media index');
        playlist.incrementCurrentMediaIndex();
      });

      console.log('play from current media: ',this.currentMedia.get('youtube_id'));

      this.sockets.in(this.get('id')).emit("media status", {
        videoId: this.currentMedia.get('youtube_id'),
        startSeconds:0,
        status:'start'
      });
      var onFire = function(elapsedTime){
        this.sockets.in(this.get('id')).emit("media status", {
          videoId: this.currentMedia.get('youtube_id'),
          startSeconds:elapsedTime,
          status:'update'
        });
      };
      var onComplete = function(){
        this.sockets.in(this.get('id')).emit("media status", {
          videoId: '',
          startSeconds:0,
          status:'stop'
        });
        this.dequeueDJ();
        this.playMedia();
      };
      var timerIncrement = 3000;
      var mediaDuration = 3 * 60; //seconds XXX replace this with media duration (YOUTUBE?)
      this.mediaTimer = new Timer(onFire.bind(this),onComplete.bind(this),timerIncrement, mediaDuration);
      this.mediaTimer.start();
    } else {
      console.log('stop media for no DJ');

      console.log('dj: ', this.djQueue[0]);

      this.sockets.in(this.get('id')).emit("media status", {
        videoId: '',
        startSeconds:0,
        status:'stop'
      });
    }
  },

  toJSON: function() {
    return _.extend((new db.Model()).toJSON.call(this), {
      users: this.users && this.users.toJSON(),
      djQueue: this.djQueue.length && this.djQueue.map(function(user) {user.toJSON();}),
      currentMedia: this.currentMedia && this.currentMedia.makeJSON(),
      mediaTimeElapsed: this.mediaTimeElapsed
    },this);
  },

  enqueueDJ: function(user_id, sockets) {
    this.sockets = sockets;
    var user = this.users.get(user_id);
    this.djQueue.push(user);
    if (this.djQueue.length === 1) {
      this.playMedia();
    }
  },

  dequeueDJ: function() {
    this.djQueue.push(this.djQueue.shift());
  },

  removeDJFromQueue: function(user_id) {
    var popDJ;
    var queueIndex;

    for (var i = 0; i < this.djQueue.length; i++) {
      var dj = this.djQueue[i];
      if (user_id === dj.get('id')) {
        popDJ = dj;
        queueIndex = i;
        break;
      }
    }

    if (queueIndex !== undefined) {
      this.djQueue.splice(queueIndex,1);
    }
    console.log('removed DJ index: ', queueIndex);

    if (queueIndex === 0) {
      this.playMedia();
    }

    return popDJ;
  },

  addUser: function(user) {
    console.log('user to add: ', user.cid);
    this.users.add(user);
  },

  removeUser: function(user_id) {
    var popUser = this.users.get(user_id);
    this.users.remove(popUser);
    return popUser;
  },

  getUser: function(user_id) {
    return this.users.get(user_id);
  }
});

module.exports = Room;
<commit_msg>(fix) change makeJSON to toJSON<commit_after>var db = require('../schema');
var Timer = require('./timer');
var _ = require('lodash');

var Users = require('../collections/users');

var Room = db.Model.extend({
  tableName: 'Rooms',
  hasTimestamps: true,

  initialize: function() {
    this.users = new Users(); //bookshelf Users collection
    this.djQueue = []; //array of bookshelf User models
    this.currentMedia = null; //bookshelf Media model
    this.mediaTimeElapsed = 0;  //seconds elapsed on currentMedia
    this.mediaTimer = null;  //Timer object
    this.sockets = null;
  },

  setSocket: function(socket) {
    this.sockets = socket;
  },

  playMedia: function() {
    if (this.mediaTimer) {
      this.mediaTimer.stop();
      this.mediaTimer = null;
    }

    if (this.djQueue[0] && this.djQueue[0].getCurrentPlaylist()) {
      var dj = this.djQueue[0];
      var playlist = dj.getCurrentPlaylist();
      this.currentMedia = playlist.getCurrentMedia();
      this.currentMedia.incrementPlayCount()
      .then(function(data) {
        console.log('incrementing media index');
        playlist.incrementCurrentMediaIndex();
      });

      console.log('play from current media: ',this.currentMedia.get('youtube_id'));

      this.sockets.in(this.get('id')).emit("media status", {
        videoId: this.currentMedia.get('youtube_id'),
        startSeconds:0,
        status:'start'
      });
      var onFire = function(elapsedTime){
        this.sockets.in(this.get('id')).emit("media status", {
          videoId: this.currentMedia.get('youtube_id'),
          startSeconds:elapsedTime,
          status:'update'
        });
      };
      var onComplete = function(){
        this.sockets.in(this.get('id')).emit("media status", {
          videoId: '',
          startSeconds:0,
          status:'stop'
        });
        this.dequeueDJ();
        this.playMedia();
      };
      var timerIncrement = 3000;
      var mediaDuration = 3 * 60; //seconds XXX replace this with media duration (YOUTUBE?)
      this.mediaTimer = new Timer(onFire.bind(this),onComplete.bind(this),timerIncrement, mediaDuration);
      this.mediaTimer.start();
    } else {
      console.log('stop media for no DJ');

      console.log('dj: ', this.djQueue[0]);

      this.sockets.in(this.get('id')).emit("media status", {
        videoId: '',
        startSeconds:0,
        status:'stop'
      });
    }
  },

  toJSON: function() {
    return _.extend((new db.Model()).toJSON.call(this), {
      users: this.users && this.users.toJSON(),
      djQueue: this.djQueue.length && this.djQueue.map(function(user) {user.toJSON();}),
      currentMedia: this.currentMedia && this.currentMedia.toJSON(),
      mediaTimeElapsed: this.mediaTimeElapsed
    },this);
  },

  enqueueDJ: function(user_id, sockets) {
    this.sockets = sockets;
    var user = this.users.get(user_id);
    this.djQueue.push(user);
    if (this.djQueue.length === 1) {
      this.playMedia();
    }
  },

  dequeueDJ: function() {
    this.djQueue.push(this.djQueue.shift());
  },

  removeDJFromQueue: function(user_id) {
    var popDJ;
    var queueIndex;

    for (var i = 0; i < this.djQueue.length; i++) {
      var dj = this.djQueue[i];
      if (user_id === dj.get('id')) {
        popDJ = dj;
        queueIndex = i;
        break;
      }
    }

    if (queueIndex !== undefined) {
      this.djQueue.splice(queueIndex,1);
    }
    console.log('removed DJ index: ', queueIndex);

    if (queueIndex === 0) {
      this.playMedia();
    }

    return popDJ;
  },

  addUser: function(user) {
    console.log('user to add: ', user.cid);
    this.users.add(user);
  },

  removeUser: function(user_id) {
    var popUser = this.users.get(user_id);
    this.users.remove(popUser);
    return popUser;
  },

  getUser: function(user_id) {
    return this.users.get(user_id);
  }
});

module.exports = Room;
<|endoftext|>


<commit_before>'use strict';

var uriRoutes, renderers,
  db = require('./services/db'),
  log = require('./services/logger').setup({
    file: __filename,
    action: 'render'
  });
const _ = require('lodash'),
  url = require('url'),
  buf = require('./services/buffer'),
  components = require('./services/components'),
  references = require('./services/references'),
  clayUtils = require('clayutils'),
  composer = require('./services/composer'),
  responses = require('./responses'),
  mapLayoutToPageData = require('./utils/layout-to-page-data');

/**
 * Check the renderers for the request extension. If the
 * request  does not have an extension, grab the default.
 *
 * @param  {Object} req
 * @return {String}
 */
function getExtension(req) {
  return _.get(req, 'params.ext', '') ? req.params.ext.toLowerCase() : renderers.default;
}

/**
 * Register renderers to be referenced.
 *
 * @param  {Object} renderObj
 */
function registerRenderers(renderObj) {
  renderers = renderObj;

  // Export the renderers object
  module.exports.renderers = renderers;
}

/**
 * Find the renderer so to use for the request.
 * @param {String|Undefined} extension
 * @return {Object}
 */
function findRenderer(extension) {
  var renderer;

  // Default to HTML if you don't have an extension
  extension = extension || renderers.default;
  // Get the renderer
  renderer = _.get(renderers, `${extension}`, null);

  if (renderer) {
    return renderer;
  } else {
    return new Error(`Renderer not found for extension ${extension}`);
  }
}

/**
 * Render a component via whatever renderer should be used
 *
 * @param  {Object} req
 * @param  {Object} res
 * @param  {Object} hrStart
 * @return {Promise}
 */
function renderComponent(req, res, hrStart) {
  var extension = getExtension(req), // Get the extension for the request
    renderer = findRenderer(extension), // Determine which renderer we are using
    _ref = req.uri,
    locals = res.locals,
    options = options || {};

  if (renderer instanceof Error) {
    log('error', renderer);
    return Promise.reject(renderer);
  }
  // Add request route params, request query params
  // and the request extension to the locals object
  locals.params = req.params;
  locals.query = req.query;
  locals.extension = extension;

  return components.get(_ref, locals)
    .then(cmptData => formDataForRenderer(cmptData, { _ref }, locals))
    .tap(logTime(hrStart, _ref))
    .then(({data, options}) => renderer.render(data, options, res))
    .catch(err => log('error', err));
}

/**
 * Grab a value from the DB and parse it before returning it
 * @param  {String} uri
 * @return {Object}
 */
function getDBObject(uri) {
  return db.get(uri);
}

/**
 * Render a page with the appropriate renderer
 * @param  {String} uri
 * @param  {Object} req
 * @param  {Object} res
 * @param  {Object} hrStart
 * @return {Promise}
 */
function renderPage(uri, req, res, hrStart) {
  const locals = res.locals,
    extension = getExtension(req),
    renderer = findRenderer(extension);

  // Add request route params, request query params
  // and the request extension to the locals object
  locals.params = req.params;
  locals.query = req.query;
  locals.extension = extension;

  // look up page alias' component instance
  return getDBObject(uri)
    .then(formDataFromLayout(locals, uri))
    .then(data => {
      logTime(hrStart, uri);
      return data;
    })
    .then(({ data, options }) => renderer.render(data, options, res))
    .catch(responses.handleError(res));
}

/**
 * Given a pageId, render the page directly from a call
 * to an Express route without trying to match to a uri
 * directly
 *
 * @param  {String} pageId
 * @return {Function}
 */
function renderDynamicRoute(pageId) {
  return (req, res) => {
    const hrStart = process.hrtime(),
      site = res.locals.site,
      pageUri = `${getExpressRoutePrefix(site)}/_pages/${pageId}@published`;

    return module.exports.renderPage(pageUri, req, res, hrStart);
  };
}

/**
 * Given a page uri, get it from the db, resolve the layout,
 * map page data to the page ares and then format all the
 * data for the renderer
 *
 * @param  {Object} locals
 * @param  {String} uri
 * @return {Function<Promise>}
 */
function formDataFromLayout(locals, uri) {
  return function (result) {
    const _layoutRef = result.layout,
      pageData = references.omitPageConfiguration(result);

    return components.get(_layoutRef, locals)
      .then(layout => mapLayoutToPageData(pageData, layout))
      .then(mappedData => module.exports.formDataForRenderer(mappedData, { _layoutRef, _ref: uri }, locals));
  };
}

/**
 * Resolve all references for nested objects and form the
 * data up in the expected object to send to a renderer
 * @param  {Object} unresolvedData
 * @param  {Object} options
 * @param  {Object} locals
 * @return {Promise}
 */
function formDataForRenderer(unresolvedData, { _layoutRef, _ref }, locals) {
  return composer.resolveComponentReferences(unresolvedData, locals)
    .then((data) => ({
      data,
      options: { locals, _ref, _layoutRef }
    }));
}

/**
 * Get the prefix that normalizes the slash on the path with the expectation that references to routes using the prefix
 * will start with a slash as well.
 *
 * @param {object} site
 * @returns {string}
 */
function getExpressRoutePrefix(site) {
  let prefix = site.host;

  if (site.path.length > 1) {
    prefix += site.path;
  }

  return prefix;
}

/**
 * Redirect to referenced type.
 *
 * Depending on what the uri references, load something different.
 *
 * @param {string} uri
 * @param {object} req
 * @param {object} res
 * @param {object} hrStart
 * @returns {Promise}
 */
function renderUri(uri, req, res, hrStart) {
  hrStart = hrStart || process.hrtime(); // Check if we actually have a start time

  return db.get(uri).then(function (result) {
    const route = _.find(uriRoutes, function (item) {
      return result.match(item.when);
    });

    if (!route) {
      throw new Error('Invalid URI: ' + uri + ': ' + result);
    }

    if (route.isUri) {
      let newBase64Uri = _.last(result.split('/')),
        newUri = buf.decode(newBase64Uri),
        newPath = url.parse(`${res.req.protocol}://${newUri}`).path,
        newUrl = `${res.req.protocol}://${res.req.hostname}${newPath}`;

      log('info', `Redirecting to ${newUrl}`);
      res.redirect(301, newUrl);
    } else {
      return route.default(result, req, res, hrStart);
    }
  });
}

/**
 * Log the timing for data composition
 *
 * @param  {Object} hrStart
 * @param  {String} uri
 * @return {Function}
 */
function logTime(hrStart, uri) {
  return () => {
    const diff = process.hrtime(hrStart),
      ms = Math.floor((diff[0] * 1e9 + diff[1]) / 1000000);

    log('info', `composed data for: ${uri} (${ms}ms)`, {
      composeTime: ms,
      uri
    });
  };
}

/**
 * Run composer by translating url to a "page" by base64ing it.  Errors are handled by Express.
 *
 * @param {Object} req
 * @param {Object} res
 * @param {Function} next
 * @return {Promise}
 */
function renderExpressRoute(req, res, next) {
  var hrStart = process.hrtime(),
    site = res.locals.site,
    prefix = `${getExpressRoutePrefix(site)}/_uris/`,
    pageReference = `${prefix}${buf.encode(req.hostname + req.baseUrl + req.path)}`;

  return module.exports.renderUri(pageReference, req, res, hrStart)
    .catch((error) => {
      if (error.name === 'NotFoundError') {
        log('error', `could not find resource ${req.uri}`, {
          message: error.message
        });
        next();
      } else {
        next(error);
      }
    });
}

/**
 * Assume they're talking about published content unless ?edit
 *
 * @param {function} fn
 * @returns {function}
 */
function assumePublishedUnlessEditing(fn) {
  return function (uri, req, res, hrStart) {
    // ignore version if they are editing; default to 'published'
    if (!_.get(res, 'req.query.edit')) {
      uri = clayUtils.replaceVersion(uri, uri.split('@')[1] || 'published');
    }

    return fn(uri, req, res, hrStart);
  };
}

/**
 * Check if a renderer exists
 *
 * @param  {String} extension
 * @return {Object|Boolean}
 */
function rendererExists(extension) {
  return _.get(renderers, `[${extension}]`, false);
}

/**
 * @param {*} value
 */
function setUriRouteHandlers(value) {
  uriRoutes = value;
}

/**
 * Uris route as following
 */
function resetUriRouteHandlers() {
  /**
   * URIs can point to many different things, and this list will probably grow.
   * @type {[{when: RegExp, html: function, json: function}]}
   */
  uriRoutes = [
    // assume published
    {
      when: /\/_pages\//,
      default: assumePublishedUnlessEditing(renderPage),
      json: assumePublishedUnlessEditing(getDBObject)
    },
    // assume published
    {
      when: /\/_components\//,
      default: assumePublishedUnlessEditing(renderComponent),
      json: assumePublishedUnlessEditing(components.get)
    },
    // uris are not published, they only exist
    {
      when: /\/_uris\//,
      isUri: true,
      html: renderUri,
      json: db.get
    }];
}

resetUriRouteHandlers();

// Rendering
module.exports = renderExpressRoute;
module.exports.renderComponent = renderComponent;
module.exports.renderPage = renderPage;
module.exports.renderUri = renderUri;
module.exports.renderDynamicRoute = renderDynamicRoute;
module.exports.formDataForRenderer = formDataForRenderer;

// Render Utils
module.exports.rendererExists = rendererExists;
module.exports.getExpressRoutePrefix = getExpressRoutePrefix;
module.exports.findRenderer = findRenderer;

// Setup
module.exports.renderers = {}; // Overriden when a user registers a render object
module.exports.registerRenderers = registerRenderers;

// For Testing
module.exports.resetUriRouteHandlers = resetUriRouteHandlers;
module.exports.setUriRouteHandlers = setUriRouteHandlers;
module.exports.assumePublishedUnlessEditing = assumePublishedUnlessEditing;
module.exports.setDb = mock => db = mock;
module.exports.setLog = mock => log = mock;<commit_msg>Show error when targeting a non-existent instance of a component with a renderer<commit_after>'use strict';

var uriRoutes, renderers,
  db = require('./services/db'),
  log = require('./services/logger').setup({
    file: __filename,
    action: 'render'
  });
const _ = require('lodash'),
  url = require('url'),
  buf = require('./services/buffer'),
  components = require('./services/components'),
  references = require('./services/references'),
  clayUtils = require('clayutils'),
  composer = require('./services/composer'),
  responses = require('./responses'),
  mapLayoutToPageData = require('./utils/layout-to-page-data');

/**
 * Check the renderers for the request extension. If the
 * request  does not have an extension, grab the default.
 *
 * @param  {Object} req
 * @return {String}
 */
function getExtension(req) {
  return _.get(req, 'params.ext', '') ? req.params.ext.toLowerCase() : renderers.default;
}

/**
 * Register renderers to be referenced.
 *
 * @param  {Object} renderObj
 */
function registerRenderers(renderObj) {
  renderers = renderObj;

  // Export the renderers object
  module.exports.renderers = renderers;
}

/**
 * Find the renderer so to use for the request.
 * @param {String|Undefined} extension
 * @return {Object}
 */
function findRenderer(extension) {
  var renderer;

  // Default to HTML if you don't have an extension
  extension = extension || renderers.default;
  // Get the renderer
  renderer = _.get(renderers, `${extension}`, null);

  if (renderer) {
    return renderer;
  } else {
    return new Error(`Renderer not found for extension ${extension}`);
  }
}

/**
 * Render a component via whatever renderer should be used
 *
 * @param  {Object} req
 * @param  {Object} res
 * @param  {Object} hrStart
 * @return {Promise}
 */
function renderComponent(req, res, hrStart) {
  var extension = getExtension(req), // Get the extension for the request
    renderer = findRenderer(extension), // Determine which renderer we are using
    _ref = req.uri,
    locals = res.locals,
    options = options || {};

  if (renderer instanceof Error) {
    log('error', renderer);
    return Promise.reject(renderer);
  }
  // Add request route params, request query params
  // and the request extension to the locals object
  locals.params = req.params;
  locals.query = req.query;
  locals.extension = extension;

  return components.get(_ref, locals)
    .then(cmptData => formDataForRenderer(cmptData, { _ref }, locals))
    .tap(logTime(hrStart, _ref))
    .then(({data, options}) => renderer.render(data, options, res))
    .catch(err => {
      const errorHandler = responses.handleError(res);

      log('error', err);

      return errorHandler(err);
    });
}

/**
 * Grab a value from the DB and parse it before returning it
 * @param  {String} uri
 * @return {Object}
 */
function getDBObject(uri) {
  return db.get(uri);
}

/**
 * Render a page with the appropriate renderer
 * @param  {String} uri
 * @param  {Object} req
 * @param  {Object} res
 * @param  {Object} hrStart
 * @return {Promise}
 */
function renderPage(uri, req, res, hrStart) {
  const locals = res.locals,
    extension = getExtension(req),
    renderer = findRenderer(extension);

  // Add request route params, request query params
  // and the request extension to the locals object
  locals.params = req.params;
  locals.query = req.query;
  locals.extension = extension;

  // look up page alias' component instance
  return getDBObject(uri)
    .then(formDataFromLayout(locals, uri))
    .then(data => {
      logTime(hrStart, uri);
      return data;
    })
    .then(({ data, options }) => renderer.render(data, options, res))
    .catch(responses.handleError(res));
}

/**
 * Given a pageId, render the page directly from a call
 * to an Express route without trying to match to a uri
 * directly
 *
 * @param  {String} pageId
 * @return {Function}
 */
function renderDynamicRoute(pageId) {
  return (req, res) => {
    const hrStart = process.hrtime(),
      site = res.locals.site,
      pageUri = `${getExpressRoutePrefix(site)}/_pages/${pageId}@published`;

    return module.exports.renderPage(pageUri, req, res, hrStart);
  };
}

/**
 * Given a page uri, get it from the db, resolve the layout,
 * map page data to the page ares and then format all the
 * data for the renderer
 *
 * @param  {Object} locals
 * @param  {String} uri
 * @return {Function<Promise>}
 */
function formDataFromLayout(locals, uri) {
  return function (result) {
    const _layoutRef = result.layout,
      pageData = references.omitPageConfiguration(result);

    return components.get(_layoutRef, locals)
      .then(layout => mapLayoutToPageData(pageData, layout))
      .then(mappedData => module.exports.formDataForRenderer(mappedData, { _layoutRef, _ref: uri }, locals));
  };
}

/**
 * Resolve all references for nested objects and form the
 * data up in the expected object to send to a renderer
 * @param  {Object} unresolvedData
 * @param  {Object} options
 * @param  {Object} locals
 * @return {Promise}
 */
function formDataForRenderer(unresolvedData, { _layoutRef, _ref }, locals) {
  return composer.resolveComponentReferences(unresolvedData, locals)
    .then((data) => ({
      data,
      options: { locals, _ref, _layoutRef }
    }));
}

/**
 * Get the prefix that normalizes the slash on the path with the expectation that references to routes using the prefix
 * will start with a slash as well.
 *
 * @param {object} site
 * @returns {string}
 */
function getExpressRoutePrefix(site) {
  let prefix = site.host;

  if (site.path.length > 1) {
    prefix += site.path;
  }

  return prefix;
}

/**
 * Redirect to referenced type.
 *
 * Depending on what the uri references, load something different.
 *
 * @param {string} uri
 * @param {object} req
 * @param {object} res
 * @param {object} hrStart
 * @returns {Promise}
 */
function renderUri(uri, req, res, hrStart) {
  hrStart = hrStart || process.hrtime(); // Check if we actually have a start time

  return db.get(uri).then(function (result) {
    const route = _.find(uriRoutes, function (item) {
      return result.match(item.when);
    });

    if (!route) {
      throw new Error('Invalid URI: ' + uri + ': ' + result);
    }

    if (route.isUri) {
      let newBase64Uri = _.last(result.split('/')),
        newUri = buf.decode(newBase64Uri),
        newPath = url.parse(`${res.req.protocol}://${newUri}`).path,
        newUrl = `${res.req.protocol}://${res.req.hostname}${newPath}`;

      log('info', `Redirecting to ${newUrl}`);
      res.redirect(301, newUrl);
    } else {
      return route.default(result, req, res, hrStart);
    }
  });
}

/**
 * Log the timing for data composition
 *
 * @param  {Object} hrStart
 * @param  {String} uri
 * @return {Function}
 */
function logTime(hrStart, uri) {
  return () => {
    const diff = process.hrtime(hrStart),
      ms = Math.floor((diff[0] * 1e9 + diff[1]) / 1000000);

    log('info', `composed data for: ${uri} (${ms}ms)`, {
      composeTime: ms,
      uri
    });
  };
}

/**
 * Run composer by translating url to a "page" by base64ing it.  Errors are handled by Express.
 *
 * @param {Object} req
 * @param {Object} res
 * @param {Function} next
 * @return {Promise}
 */
function renderExpressRoute(req, res, next) {
  var hrStart = process.hrtime(),
    site = res.locals.site,
    prefix = `${getExpressRoutePrefix(site)}/_uris/`,
    pageReference = `${prefix}${buf.encode(req.hostname + req.baseUrl + req.path)}`;

  return module.exports.renderUri(pageReference, req, res, hrStart)
    .catch((error) => {
      if (error.name === 'NotFoundError') {
        log('error', `could not find resource ${req.uri}`, {
          message: error.message
        });
        next();
      } else {
        next(error);
      }
    });
}

/**
 * Assume they're talking about published content unless ?edit
 *
 * @param {function} fn
 * @returns {function}
 */
function assumePublishedUnlessEditing(fn) {
  return function (uri, req, res, hrStart) {
    // ignore version if they are editing; default to 'published'
    if (!_.get(res, 'req.query.edit')) {
      uri = clayUtils.replaceVersion(uri, uri.split('@')[1] || 'published');
    }

    return fn(uri, req, res, hrStart);
  };
}

/**
 * Check if a renderer exists
 *
 * @param  {String} extension
 * @return {Object|Boolean}
 */
function rendererExists(extension) {
  return _.get(renderers, `[${extension}]`, false);
}

/**
 * @param {*} value
 */
function setUriRouteHandlers(value) {
  uriRoutes = value;
}

/**
 * Uris route as following
 */
function resetUriRouteHandlers() {
  /**
   * URIs can point to many different things, and this list will probably grow.
   * @type {[{when: RegExp, html: function, json: function}]}
   */
  uriRoutes = [
    // assume published
    {
      when: /\/_pages\//,
      default: assumePublishedUnlessEditing(renderPage),
      json: assumePublishedUnlessEditing(getDBObject)
    },
    // assume published
    {
      when: /\/_components\//,
      default: assumePublishedUnlessEditing(renderComponent),
      json: assumePublishedUnlessEditing(components.get)
    },
    // uris are not published, they only exist
    {
      when: /\/_uris\//,
      isUri: true,
      html: renderUri,
      json: db.get
    }];
}

resetUriRouteHandlers();

// Rendering
module.exports = renderExpressRoute;
module.exports.renderComponent = renderComponent;
module.exports.renderPage = renderPage;
module.exports.renderUri = renderUri;
module.exports.renderDynamicRoute = renderDynamicRoute;
module.exports.formDataForRenderer = formDataForRenderer;

// Render Utils
module.exports.rendererExists = rendererExists;
module.exports.getExpressRoutePrefix = getExpressRoutePrefix;
module.exports.findRenderer = findRenderer;

// Setup
module.exports.renderers = {}; // Overriden when a user registers a render object
module.exports.registerRenderers = registerRenderers;

// For Testing
module.exports.resetUriRouteHandlers = resetUriRouteHandlers;
module.exports.setUriRouteHandlers = setUriRouteHandlers;
module.exports.assumePublishedUnlessEditing = assumePublishedUnlessEditing;
module.exports.setDb = mock => db = mock;
module.exports.setLog = mock => log = mock;
<|endoftext|>


<commit_before>var mapnik = require('mapnik');
var _ = require('underscore');
var Step = require('step');
var mime = require('mime')

var MapnikSource = require('./mapnik_backend');

var EARTH_RADIUS = 6378137;
var EARTH_DIAMETER = EARTH_RADIUS * 2;
var EARTH_CIRCUMFERENCE = EARTH_DIAMETER * Math.PI;
var MAX_RES = EARTH_CIRCUMFERENCE / 256;
var ORIGIN_SHIFT = EARTH_CIRCUMFERENCE/2;

exports['calculateMetatile'] = calculateMetatile;
function calculateMetatile(options) {
    var z = +options.z, x = +options.x, y = +options.y;
    var total = 1 << z;
    var resolution = MAX_RES / total;

    // Make sure we start at a metatile boundary.
    x -= x % options.metatile;
    y -= y % options.metatile;

    // Make sure we don't calculcate a metatile that is larger than the bounds.
    var metaWidth  = Math.min(options.metatile, total, total - x);
    var metaHeight = Math.min(options.metatile, total, total - y);

    // Generate all tile coordinates that are within the metatile.
    var tiles = [];
    for (var dx = 0; dx < metaWidth; dx++) {
        for (var dy = 0; dy < metaHeight; dy++) {
            tiles.push([ z, x + dx, y + dy ]);
        }
    }

    var minx = (x * 256) * resolution - ORIGIN_SHIFT;
    var miny = -((y + metaHeight) * 256) * resolution + ORIGIN_SHIFT;
    var maxx = ((x + metaWidth) * 256) * resolution - ORIGIN_SHIFT;
    var maxy = -((y * 256) * resolution - ORIGIN_SHIFT);
    return {
        width: metaWidth * options.tileSize,
        height: metaHeight * options.tileSize,
        x: x, y: y,
        tiles: tiles,
        bbox: [ minx, miny, maxx, maxy ]
    };
}

exports['sliceMetatile'] = sliceMetatile;
function sliceMetatile(source, image, options, meta, callback) {
    var tiles = {};

    Step(function() {
        var group = this.group();
        meta.tiles.forEach(function(c) {
            var next = group();
            var key = [options.format, c[0], c[1], c[2]].join(',');
            getImage(source,
                     image,
                     options,
                     (c[1] - meta.x) * options.tileSize,
                     (c[2] - meta.y) * options.tileSize,
                     function(err, image) {
                tiles[key] = {
                    image: image,
                    headers: options.headers
                };
                next();
            });
        });
    }, function(err) {
        if (err) return callback(err);
        callback(null, tiles);
    });
}

exports['encodeSingleTile'] = encodeSingleTile;
function encodeSingleTile(source, image, options, meta, callback) {
    var tiles = {};
    var key = [options.format, options.z, options.x, options.y].join(',');
    getImage(source, image, options, 0, 0, function(err, image) {
        if (err) return callback(err);
        tiles[key] = { image: image, headers: options.headers };
        callback(null, tiles);
    });
}

function getImage(source, image, options, x, y, callback) {
    var view = image.view(x, y, options.tileSize, options.tileSize);
    view.isSolid(function(err, solid, pixel) {
        if (err) return callback(err);
        var pixel_key = '';
        if (solid) {
            if (options.format === 'utf') {
                // TODO https://github.com/mapbox/tilelive-mapnik/issues/56
                pixel_key = pixel.toString();
            } else {
                // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators
                var a = (pixel>>>24) & 0xff;
                var r = pixel & 0xff;
                var g = (pixel>>>8) & 0xff;
                var b = (pixel>>>16) & 0xff;
                pixel_key = r +','+ g + ',' + b + ',' + a;
            }
        }
        // Add stats.
        options.source._stats.total++;
        if (solid !== false) options.source._stats.solid++;
        if (solid !== false && image.painted()) options.source._stats.solidPainted++;
        // If solid and image buffer is cached skip image encoding.
        if (solid && source.solidCache[pixel_key]) return callback(null, source.solidCache[pixel_key]);
        // Note: the second parameter is needed for grid encoding.
        options.source._stats.encoded++;
        try {
            view.encode(options.format, options, function(err, buffer) {
                if (err) {
                    return callback(err);
                }
                if (solid !== false) {
                    // @TODO for 'utf' this attaches an extra, bogus 'solid' key to
                    // to the grid as it is not a buffer but an actual JS object.
                    // Fix is to propagate a third parameter through callbacks all
                    // the way back to tilelive source #getGrid.
                    buffer.solid = pixel_key;
                    source.solidCache[pixel_key] = buffer;
                }
                return callback(null, buffer);
            });
        } catch (err) {
            return callback(err);
        }
    });
}

// Render png/jpg/tif image or a utf grid and return an encoded buffer
MapnikSource.prototype._renderMetatile = function(options, callback) {
    var source = this;

    // Calculate bbox from xyz, respecting metatile settings.
    var meta = calculateMetatile(options);

    // Set default options.
    if (options.format === 'utf') {
        options.layer = source._info.interactivity_layer;
        options.fields = source._info.interactivity_fields;
        options.resolution = source._uri.query.resolution;
        options.headers = { 'Content-Type': 'text/javascript; charset=utf-8' };
        var image = new mapnik.Grid(meta.width, meta.height);
    } else {
        // NOTE: formats use mapnik syntax like `png8:m=h` or `jpeg80`
        // so we need custom handling for png/jpeg
        if (options.format.indexOf('png') != -1) {
            options.headers = { 'Content-Type': 'image/png' };
        } else if (options.format.indexOf('jpeg') != -1 ||
                   options.format.indexOf('jpg') != -1) {
            options.headers = { 'Content-Type': 'image/jpeg' };
        } else {
            // will default to 'application/octet-stream' if unable to detect
            options.headers = { 'Content-Type': mime.lookup(options.format.split(':')[0]) };
        }
        var image = new mapnik.Image(meta.width, meta.height);
    }

    options.scale = +source._uri.query.scale;

    // Add reference to the source allowing debug/stat reporting to be compiled.
    options.source = source;

    process.nextTick(function() {
        // acquire can throw if pool is draining
        try {
            source._pool.acquire(function(err, map) {
                if (err) {
                    return callback(err);
                }
                // Begin at metatile boundary.
                options.x = meta.x;
                options.y = meta.y;
                map.resize(meta.width, meta.height);
                map.extent = meta.bbox;
                // todo - handle per tile bufferSize: https://github.com/mapnik/node-mapnik/issues/175
                try {
                    source._stats.render++;
                    map.render(image, options, function(err, image) {
                        process.nextTick(function() {
                            // Release after the .render() callback returned
                            // to avoid mapnik errors.
                            source._pool.release(map);
                        });
                        if (err) return callback(err);
                        if (meta.tiles.length > 1) {
                            sliceMetatile(source, image, options, meta, callback);
                        } else {
                            encodeSingleTile(source, image, options, meta, callback);
                        }
                    });
                } catch(err) {
                    process.nextTick(function() {
                        // Release after the .render() callback returned
                        // to avoid mapnik errors.
                        source._pool.release(map);
                    });
                    return callback(err);
                }
            });
        } catch (err) {
            return callback(err);
        }
    });

    // Return a list of all the tile coordinates that are being rendered
    // as part of this metatile.
    return meta.tiles.map(function(tile) {
        return options.format + ',' + tile.join(',');
    });
};
<commit_msg>key image catch on format as well as rgba - refs #51<commit_after>var mapnik = require('mapnik');
var _ = require('underscore');
var Step = require('step');
var mime = require('mime')

var MapnikSource = require('./mapnik_backend');

var EARTH_RADIUS = 6378137;
var EARTH_DIAMETER = EARTH_RADIUS * 2;
var EARTH_CIRCUMFERENCE = EARTH_DIAMETER * Math.PI;
var MAX_RES = EARTH_CIRCUMFERENCE / 256;
var ORIGIN_SHIFT = EARTH_CIRCUMFERENCE/2;

exports['calculateMetatile'] = calculateMetatile;
function calculateMetatile(options) {
    var z = +options.z, x = +options.x, y = +options.y;
    var total = 1 << z;
    var resolution = MAX_RES / total;

    // Make sure we start at a metatile boundary.
    x -= x % options.metatile;
    y -= y % options.metatile;

    // Make sure we don't calculcate a metatile that is larger than the bounds.
    var metaWidth  = Math.min(options.metatile, total, total - x);
    var metaHeight = Math.min(options.metatile, total, total - y);

    // Generate all tile coordinates that are within the metatile.
    var tiles = [];
    for (var dx = 0; dx < metaWidth; dx++) {
        for (var dy = 0; dy < metaHeight; dy++) {
            tiles.push([ z, x + dx, y + dy ]);
        }
    }

    var minx = (x * 256) * resolution - ORIGIN_SHIFT;
    var miny = -((y + metaHeight) * 256) * resolution + ORIGIN_SHIFT;
    var maxx = ((x + metaWidth) * 256) * resolution - ORIGIN_SHIFT;
    var maxy = -((y * 256) * resolution - ORIGIN_SHIFT);
    return {
        width: metaWidth * options.tileSize,
        height: metaHeight * options.tileSize,
        x: x, y: y,
        tiles: tiles,
        bbox: [ minx, miny, maxx, maxy ]
    };
}

exports['sliceMetatile'] = sliceMetatile;
function sliceMetatile(source, image, options, meta, callback) {
    var tiles = {};

    Step(function() {
        var group = this.group();
        meta.tiles.forEach(function(c) {
            var next = group();
            var key = [options.format, c[0], c[1], c[2]].join(',');
            getImage(source,
                     image,
                     options,
                     (c[1] - meta.x) * options.tileSize,
                     (c[2] - meta.y) * options.tileSize,
                     function(err, image) {
                tiles[key] = {
                    image: image,
                    headers: options.headers
                };
                next();
            });
        });
    }, function(err) {
        if (err) return callback(err);
        callback(null, tiles);
    });
}

exports['encodeSingleTile'] = encodeSingleTile;
function encodeSingleTile(source, image, options, meta, callback) {
    var tiles = {};
    var key = [options.format, options.z, options.x, options.y].join(',');
    getImage(source, image, options, 0, 0, function(err, image) {
        if (err) return callback(err);
        tiles[key] = { image: image, headers: options.headers };
        callback(null, tiles);
    });
}

function getImage(source, image, options, x, y, callback) {
    var view = image.view(x, y, options.tileSize, options.tileSize);
    view.isSolid(function(err, solid, pixel) {
        if (err) return callback(err);
        var pixel_key = '';
        if (solid) {
            if (options.format === 'utf') {
                // TODO https://github.com/mapbox/tilelive-mapnik/issues/56
                pixel_key = pixel.toString();
            } else {
                // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/Bitwise_Operators
                var a = (pixel>>>24) & 0xff;
                var r = pixel & 0xff;
                var g = (pixel>>>8) & 0xff;
                var b = (pixel>>>16) & 0xff;
                pixel_key = options.format + r +','+ g + ',' + b + ',' + a;
            }
        }
        // Add stats.
        options.source._stats.total++;
        if (solid !== false) options.source._stats.solid++;
        if (solid !== false && image.painted()) options.source._stats.solidPainted++;
        // If solid and image buffer is cached skip image encoding.
        if (solid && source.solidCache[pixel_key]) return callback(null, source.solidCache[pixel_key]);
        // Note: the second parameter is needed for grid encoding.
        options.source._stats.encoded++;
        try {
            view.encode(options.format, options, function(err, buffer) {
                if (err) {
                    return callback(err);
                }
                if (solid !== false) {
                    // @TODO for 'utf' this attaches an extra, bogus 'solid' key to
                    // to the grid as it is not a buffer but an actual JS object.
                    // Fix is to propagate a third parameter through callbacks all
                    // the way back to tilelive source #getGrid.
                    buffer.solid = pixel_key;
                    source.solidCache[pixel_key] = buffer;
                }
                return callback(null, buffer);
            });
        } catch (err) {
            return callback(err);
        }
    });
}

// Render png/jpg/tif image or a utf grid and return an encoded buffer
MapnikSource.prototype._renderMetatile = function(options, callback) {
    var source = this;

    // Calculate bbox from xyz, respecting metatile settings.
    var meta = calculateMetatile(options);

    // Set default options.
    if (options.format === 'utf') {
        options.layer = source._info.interactivity_layer;
        options.fields = source._info.interactivity_fields;
        options.resolution = source._uri.query.resolution;
        options.headers = { 'Content-Type': 'text/javascript; charset=utf-8' };
        var image = new mapnik.Grid(meta.width, meta.height);
    } else {
        // NOTE: formats use mapnik syntax like `png8:m=h` or `jpeg80`
        // so we need custom handling for png/jpeg
        if (options.format.indexOf('png') != -1) {
            options.headers = { 'Content-Type': 'image/png' };
        } else if (options.format.indexOf('jpeg') != -1 ||
                   options.format.indexOf('jpg') != -1) {
            options.headers = { 'Content-Type': 'image/jpeg' };
        } else {
            // will default to 'application/octet-stream' if unable to detect
            options.headers = { 'Content-Type': mime.lookup(options.format.split(':')[0]) };
        }
        var image = new mapnik.Image(meta.width, meta.height);
    }

    options.scale = +source._uri.query.scale;

    // Add reference to the source allowing debug/stat reporting to be compiled.
    options.source = source;

    process.nextTick(function() {
        // acquire can throw if pool is draining
        try {
            source._pool.acquire(function(err, map) {
                if (err) {
                    return callback(err);
                }
                // Begin at metatile boundary.
                options.x = meta.x;
                options.y = meta.y;
                map.resize(meta.width, meta.height);
                map.extent = meta.bbox;
                // todo - handle per tile bufferSize: https://github.com/mapnik/node-mapnik/issues/175
                try {
                    source._stats.render++;
                    map.render(image, options, function(err, image) {
                        process.nextTick(function() {
                            // Release after the .render() callback returned
                            // to avoid mapnik errors.
                            source._pool.release(map);
                        });
                        if (err) return callback(err);
                        if (meta.tiles.length > 1) {
                            sliceMetatile(source, image, options, meta, callback);
                        } else {
                            encodeSingleTile(source, image, options, meta, callback);
                        }
                    });
                } catch(err) {
                    process.nextTick(function() {
                        // Release after the .render() callback returned
                        // to avoid mapnik errors.
                        source._pool.release(map);
                    });
                    return callback(err);
                }
            });
        } catch (err) {
            return callback(err);
        }
    });

    // Return a list of all the tile coordinates that are being rendered
    // as part of this metatile.
    return meta.tiles.map(function(tile) {
        return options.format + ',' + tile.join(',');
    });
};
<|endoftext|>


<commit_before>const db = require('sqlite')
const squel = require('squel')
const debug = require('debug')
const log = debug('app:library:get')

async function getLibrary (find = {}, providerData = false) {
  let artists = {
    result: [],
    entities: {}
  }
  let songs = {
    result: [],
    entities: {}
  }

  // first query: songs
  try {
    const q = squel.select()
      .field('songId, artistId, title, duration, provider')
      .field('COUNT(stars.userId) AS stars')
      .from('songs')
      .left_join('stars USING(songId)')
      .group('songId')
      .order('title')

    // off by default since it requires extra processing
    if (providerData) {
      q.field('providerData')
    }

    // artistId filter
    if (typeof find.artistId !== 'undefined') {
      q.where('artistId = ?', find.artistId)
    }

    // other filters
    Object.keys(find).map(key => {
      if (key === 'providerData' && typeof find.providerData === 'object') {
        Object.keys(find.providerData).map(i => {
          q.where(`json_extract(providerData, '$.${i}') = ?`, find.providerData[i])
        })
      } else if (key !== 'artistId') {
        q.where(`${key} = ?`, find[key])
      }
    })

    // log(q.toString())
    const { text, values } = q.toParam()
    const rows = await db.all(text, values)

    // normalize results
    rows.forEach(function (row) {
      if (providerData) {
        row.providerData = JSON.parse(row.providerData)
      }

      songs.result.push(row.songId)
      songs.entities[row.songId] = row

      // used in library view as parent/child LUT
      if (typeof artists.entities[row.artistId] === 'undefined') {
        artists.entities[row.artistId] = {
          songIds: [],
        }
      }

      artists.entities[row.artistId].songIds.push(row.songId)
    })
  } catch (err) {
    log(err.message)
    return Promise.reject(err)
  }

  // second query: artists
  try {
    const q = squel.select()
      .from('artists')
      .order('name')

    // artistId filter
    if (typeof find.artistId !== 'undefined') {
      q.where('artistId = ?', find.artistId)
      delete find.artistId
    }

    // if other filters are present we'll need to
    // only include songs returned in first query
    if (Object.keys(find).length) {
      q.where('artistId IN ?', songs.result.map(songId => songs.entities[songId].artistId))
    }

    // log(q.toString())
    const { text, values } = q.toParam()
    const rows = await db.all(text, values)

    // normalize results
    rows.forEach(function (row) {
      artists.result.push(row.artistId)
      // merge with the LUT from first query
      artists.entities[row.artistId] = Object.assign(artists.entities[row.artistId], row)
    })
  } catch (err) {
    log(err.message)
    return Promise.reject(err)
  }

  log('%s songs by %s artists', songs.result.length, artists.result.length)
  return { artists, songs }
}

module.exports = getLibrary
<commit_msg>Initial rewrite of getLibrary for the new media table<commit_after>const db = require('sqlite')
const squel = require('squel')
const debug = require('debug')
const log = debug('app:library:get')

/**
 * Gets all artists/songs without detailed media info
 * (suitable for pushing out to non-admin users)
 *
 * @return {[object]}          Results normalized by artist and song
 */
async function getLibrary () {
  const artists = {
    result: [],
    entities: {}
  }
  const songs = {
    result: [],
    entities: {}
  }

  // First query: artists
  try {
    const q = squel.select()
      .from('artists')
      .order('name')

    // log(q.toString())
    const { text, values } = q.toParam()
    const rows = await db.all(text, values)

    // normalize results
    for (const row of rows) {
      artists.result.push(row.artistId)
      artists.entities[row.artistId] = row
      // prep LUT for songIds
      artists.entities[row.artistId].songIds = []
    }
  } catch (err) {
    log(err.message)
    return Promise.reject(err)
  }

  // Second query: media/songs
  //
  // This could probably be improved but writing a single query that properly
  // returns the right media item while handling enabled/disabled providers,
  // provider priority, per-song preferred media (with fallback if preferred
  // media's provider is disabled), etc. proved difficult. At the very least
  // this query avoids subqueries and should be relatively performant.
  try {
    const q = squel.select()
      .field('media.mediaId, media.songId, media.duration, media.provider, media.isPreferred')
      .field('songs.artistId, songs.title')
      .field('COUNT(stars.userId) AS numStars')
      .from('media')
      .join('providers ON (providers.name = media.provider)')
      .join('songs USING(songId)')
      .left_join('stars USING(songId)')
      .where('providers.isEnabled = 1')
      .group('mediaId')
      .order('providers.priority')
      .order('songs.title')

    const { text, values } = q.toParam()
    const rows = await db.all(text, values)

    // normalize and process results
    for (const row of rows) {
      if (typeof songs.entities[row.songId] === 'undefined') {
        // add songId to artist's LUT
        artists.entities[row.artistId].songIds.push(row.songId)

        songs.result.push(row.songId)
        songs.entities[row.songId] = row
        songs.entities[row.songId].numMedia = 1
      } else if (row.isPreffered === 1) {
        // The previous query should have returned media ordered by the
        // correct provider priority, so we only want to overwrite an
        // exisiting entry if this media's isPreferred flag is set.
        songs.entities[row.songId] = row
      }

      // increment song's media item count
      ++songs.entities[row.songId].numMedia
    }
  } catch (err) {
    log(err.message)
    return Promise.reject(err)
  }

  log('%s songs by %s artists', songs.result.length, artists.result.length)
  return { artists, songs }
}

module.exports = getLibrary
<|endoftext|>