<commit_before>function deleteCheckSubmit() {

    if (confirm('Are you sure?')) {
        document.forms['deletemenuform'].submit();
    } else {
        return false;
    }
}

function HutLocation(name, lat, lng, imgurl, showurl, linktext, description, organization) {
    this.name = name;
    this.lat = lat;
    this.lng = lng;
    this.showurl = showurl;
    this.imgurl = imgurl;
    this.linktext = linktext;
    this.description = description;
    this.organization = organization;
}

function toggleMapList() {
    var oldList = document.getElementById("oldlist");
    var map = document.getElementById("map");

    if (map.style.display == "none") {
        map.style.display = "";
        oldList.style.display = "none";
    } else {
        map.style.display = "none";
        oldList.style.display = "";
    }
}

function toggleMapView() {
    var img = document.getElementById("hutimg");
    var map = document.getElementById("map");

    if (map.style.display == "none") {
        map.style.display = "";
        img.style.display = "none";
    } else {
        map.style.display = "none";
        img.style.display = "";
    }
}

function initializeMaps(hutlocs) {
    //    var norway = new GLatLng(64.830253743883, 16.2158203125);
    var norway = new GLatLng(60, 10.7);

    if (GBrowserIsCompatible()) {
        var map = new google.maps.Map2(document.getElementById("map"));

        map.setCenter(norway, 7);

        map.addControl(new GLargeMapControl());
        map.addControl(new GMapTypeControl());
    } else {
        toggleMapList();
    }

    var bounds = new GLatLngBounds();

    for (name in hutlocs) {
        var hutloc = hutlocs[name];

        var marker = getMarker(hutloc, bounds);

        map.addOverlay(marker);
    }

    map.setZoom(map.getBoundsZoomLevel(bounds));

    map.setCenter(bounds.getCenter());
}

function initializeSingleMap(lat, lng) {
    var point = new GLatLng(lat, lng);

    if (GBrowserIsCompatible()) {
        var map = new google.maps.Map2(document.getElementById("map"));

        map.setCenter(point, 7);

        map.addControl(new GSmallMapControl());
        map.addControl(new GMapTypeControl());
    }

    if (document.getElementById("hutimg")) {
        toggleMapView();
    }

    var marker = new GMarker(point);

    map.addOverlay(marker);
}

function getMarker(hutloc, bounds) {
    var latlng = new GLatLng(hutloc.lat, hutloc.lng);
    var marker = new GMarker(latlng);

    bounds.extend(latlng);

    var popup = "
" + hutloc.name + "
";

    if (hutloc.organization) {
        popup += "
" + hutloc.organization + "
";
    }

    if (hutloc.imgurl) {
        popup += "";
    }

    popup += "
" + hutloc.description + "
";
    popup += "
" + hutloc.linktext + "
";

    GEvent.addListener(marker, "click", function() {
        marker.openInfoWindowHtml(popup);
    });

    return marker;
}
<commit_msg>[svn] Backport fix to #54<commit_after>function deleteCheckSubmit() {

    if (confirm('Are you sure?')) {
        document.forms['deletemenuform'].submit();
    } else {
        return false;
    }
}

function HutLocation(name, lat, lng, imgurl, showurl, linktext, description, organization) {
    this.name = name;
    this.lat = lat;
    this.lng = lng;
    this.showurl = showurl;
    this.imgurl = imgurl;
    this.linktext = linktext;
    this.description = description;
    this.organization = organization;
}

function toggleMapList() {
    var oldList = document.getElementById("oldlist");
    var map = document.getElementById("map");

    if (map.style.display == "none") {
        map.style.display = "";
        oldList.style.display = "none";
    } else {
        map.style.display = "none";
        oldList.style.display = "";
    }
}

function toggleMapView() {
    var img = document.getElementById("hutimg");
    var map = document.getElementById("map");

    if (map.style.display == "none") {
        map.style.display = "";
        img.style.display = "none";
    } else {
        map.style.display = "none";
        img.style.display = "";
    }
}

function initializeMaps(hutlocs) {
    //    var norway = new GLatLng(64.830253743883, 16.2158203125);
    var norway = new GLatLng(60, 10.7);

    if (GBrowserIsCompatible()) {
        var map = new google.maps.Map2(document.getElementById("map"));

        map.setCenter(norway, 7);

        map.addControl(new GLargeMapControl());
        map.addControl(new GMapTypeControl());
    } else {
        toggleMapList();
    }

    var bounds = new GLatLngBounds();

    for (name in hutlocs) {
        var hutloc = hutlocs[name];

        var marker = getMarker(hutloc, bounds);

        map.addOverlay(marker);
    }

    // Zoom to bounds
    map.setZoom(map.getBoundsZoomLevel(bounds));
    map.setCenter(bounds.getCenter());

    // Based on that - grow top by a small amount
    bounds = growTopBound(map, bounds);

    // And re-do the zoom
    map.setZoom(map.getBoundsZoomLevel(bounds));
    map.setCenter(bounds.getCenter());
}

function initializeSingleMap(lat, lng) {
    var point = new GLatLng(lat, lng);

    if (GBrowserIsCompatible()) {
        var map = new google.maps.Map2(document.getElementById("map"));

        map.setCenter(point, 7);

        map.addControl(new GSmallMapControl());
        map.addControl(new GMapTypeControl());
    }

    if (document.getElementById("hutimg")) {
        toggleMapView();
    }

    var marker = new GMarker(point);

    map.addOverlay(marker);
}

function getMarker(hutloc, bounds) {
    var latlng = new GLatLng(hutloc.lat, hutloc.lng);
    var marker = new GMarker(latlng);

    bounds.extend(latlng);

    var popup = "
" + hutloc.name + "
";

    if (hutloc.organization) {
        popup += "
" + hutloc.organization + "
";
    }

    if (hutloc.imgurl) {
        popup += "";
    }

    popup += "
" + hutloc.description + "
";
    popup += "
" + hutloc.linktext + "
";

    GEvent.addListener(marker, "click", function() {
        marker.openInfoWindowHtml(popup);
    });

    return marker;
}

function growTopBound(map, bounds) {
    var latlngNorthEast = bounds.getNorthEast();

    var pointNorthEast = map.fromLatLngToDivPixel(latlngNorthEast);

    bounds.extend(map.fromDivPixelToLatLng(new GPoint(pointNorthEast.x, pointNorthEast.y - 75)));

    return bounds;
}
<|endoftext|>


<commit_before>// TODO:
// movimentação da bola
// colisões com blocos
// remover bloco quando colidir -> setar posição na matriz para null
// pontuação
// gráficos
// o que mais for necessário
var game = {
	canvas : null, // Canvas
	context : null, // Objetos do HTML5 Canvas
	height : null,
	width : null,
	tecla : null, // tecla sendo pressionada no momento (esquerda/direita)
	blocos : [], // matriz bidimensional para guardar os blocos
	numLinhas : 5, // 5 linhas, 10 blocos por linha
	numColunas : 10, //número de linhas e colunas dos blocos, largura: 60, altura: 20;

	init : function(){

		game.setup();

		// Intervalo do Gameloop: 30 fps
		setInterval(game.gameLoop, 1000 / 30);
	},

	setup : function(){
		game.canvas = document.getElementById("canvas");
		game.context = game.canvas.getContext("2d");
		game.height = game.canvas.height;
		game.width = game.canvas.width;

		jogador.x = game.width / 2 - jogador.w / 2;
		jogador.y = game.height - jogador.h;

		bola.x = canvas.width / 2;
		bola.y = jogador.y - bola.raio;

		// Blocos
		for(var linha = 0; linha < game.numLinhas; linha++){
			game.blocos[linha] = [];
			for(var coluna = 0; coluna < game.numColunas; coluna++){
				game.blocos[linha][coluna] = {
					x: 60 * coluna, // X varia junto com a coluna
					y: 20 * linha, // Y varia junto com a linha
					w: 60, //largura
					h: 20  //altura
				}
			}
		}

		// Teclas pressionadas
		game.tecla = {
			direita: false,
			esquerda: false
		}

		game.bind();
	},

	bind : function(){
		// Eventos
		document.addEventListener('keyup', game.keyUp, false);
		document.addEventListener('keydown', game.keyDown, false);
	},

	// Códigos de teclas:
	// Esquerda: 37
	// Direita: 39
	 keyDown : function(e){
		if(e.keyCode == 37){
			game.tecla.esquerda = true;
		}
		else if(e.keyCode == 39){
			game.tecla.direita = true;
		}
	},

	 keyUp : function(e){
		if(e.keyCode == 37){
			game.tecla.esquerda = false;
		}
		else if(e.keyCode == 39){
			game.tecla.direita = false;
		}
	},

	gameLoop : function(){
		// Processar entrada do jogador e atualizar estado do jogo
		game.atualizarEstadoJogo();
		// Atualizar tela do jogo
		game.atualizarTela();
	},

	 atualizarEstadoJogo : function(){
		// Movimentação jogador
		if(game.tecla.direita != game.tecla.esquerda){
			if(game.tecla.esquerda){
				if(jogador.x > 0){
					jogador.x -= jogador.velocidade;
				}
			}
			else {
				if(jogador.x < canvas.width - jogador.w){
					jogador.x += jogador.velocidade;
				}
			}
		}
	},

	desenharBloco : function(linha, coluna){
		// desenhar apenas blocos que não sejam null
		// (ou seja, não colidiram com a bola)
		// cada linha terá uma cor, como o Breakout de Atari
		game.bloco = game.blocos[linha][coluna];
		if(game.bloco != null) {
			// devemos adicionar um contorno?
			//context.strokeRect(bloco.x, bloco.y, bloco.w, bloco.h); //contorno
			if(linha == 0) game.context.fillStyle = "green";
			else if(linha == 1) game.context.fillStyle = "blue";
			else if(linha == 2) game.context.fillStyle = "red";
			else if(linha == 3) game.context.fillStyle = "yellow";
			else if(linha == 4) game.context.fillStyle = "orange";
			game.context.fillRect(game.bloco.x, game.bloco.y, game.bloco.w, game.bloco.h); //preenchimento
		}
	},

	atualizarTela : function(){
		// Limpeza da tela
		game.context.clearRect(0, 0, game.width, game.height);

		// Jogador
		game.context.fillStyle = "black";
		game.context.fillRect(jogador.x, jogador.y, jogador.w, jogador.h);

		// Bola
		game.context.fillStyle = "red";
		game.context.beginPath();
		game.context.arc(bola.x, bola.y, bola.raio, 0, Math.PI*2, true);
		game.context.closePath();
		game.context.fill();

		// Blocos - desenhados linha por linha
		for(var linha = 0; linha < game.numLinhas; linha++){
			for(var coluna = 0; coluna < game.numColunas; coluna++){
				game.desenharBloco(linha, coluna);
			}
		}
	}
};<commit_msg>Breakout: Adicionando movimentação e colisões com a parede para a bola<commit_after>// TODO:
// movimentação da bola
// colisões com blocos
// remover bloco quando colidir -> setar posição na matriz para null
// pontuação
// gráficos
// o que mais for necessário
var game = {
	canvas : null, // Canvas
	context : null, // Objetos do HTML5 Canvas
	height : null,
	width : null,
	tecla : null, // tecla sendo pressionada no momento (esquerda/direita)
	blocos : [], // matriz bidimensional para guardar os blocos
	numLinhas : 5, // 5 linhas, 10 blocos por linha
	numColunas : 10, //número de linhas e colunas dos blocos, largura: 60, altura: 20;

	init : function(){

		game.setup();

		// Intervalo do Gameloop: 30 fps
		setInterval(game.gameLoop, 1000 / 30);
	},

	setup : function(){
		game.canvas = document.getElementById("canvas");
		game.context = game.canvas.getContext("2d");
		game.height = game.canvas.height;
		game.width = game.canvas.width;

		jogador.x = game.width / 2 - jogador.w / 2;
		jogador.y = game.height - jogador.h;

		bola.x = canvas.width / 2;
		bola.y = jogador.y - bola.raio;

		// Blocos
		for(var linha = 0; linha < game.numLinhas; linha++){
			game.blocos[linha] = [];
			for(var coluna = 0; coluna < game.numColunas; coluna++){
				game.blocos[linha][coluna] = {
					x: 60 * coluna, // X varia junto com a coluna
					y: 20 * linha, // Y varia junto com a linha
					w: 60, //largura
					h: 20  //altura
				}
			}
		}

		// Teclas pressionadas
		game.tecla = {
			direita: false,
			esquerda: false
		}

		game.bind();
	},

	bind : function(){
		// Eventos
		document.addEventListener('keyup', game.keyUp, false);
		document.addEventListener('keydown', game.keyDown, false);
	},

	// Códigos de teclas:
	// Esquerda: 37
	// Direita: 39
	 keyDown : function(e){
		if(e.keyCode == 37){
			game.tecla.esquerda = true;
		}
		else if(e.keyCode == 39){
			game.tecla.direita = true;
		}
	},

	 keyUp : function(e){
		if(e.keyCode == 37){
			game.tecla.esquerda = false;
		}
		else if(e.keyCode == 39){
			game.tecla.direita = false;
		}
	},

	gameLoop : function(){
		// Processar entrada do jogador e atualizar estado do jogo
		game.atualizarEstadoJogo();
		// Atualizar tela do jogo
		game.atualizarTela();
	},

	 atualizarEstadoJogo : function(){
		// Movimentação jogador
		if(game.tecla.direita != game.tecla.esquerda){
			if(game.tecla.esquerda){
				if(jogador.x > 0){
					jogador.x -= jogador.velocidade;
				}
			}
			else {
				if(jogador.x < canvas.width - jogador.w){
					jogador.x += jogador.velocidade;
				}
			}
		}
		// Movimentação bola
		if(bola.baixo){
			// Colisão jogador
			if(bola.y + bola.raio >= jogador.y && bola.x - bola.raio >= jogador.x && bola.x + bola.raio <= jogador.x + jogador.w){
				bola.baixo = false;
			}
			bola.y += bola.velocidade;
		}
		else {
			bola.y -= bola.velocidade;
		}
		// Colisão paredes
		if(bola.x - bola.raio <= 0 || bola.x + bola.raio >= canvas.width){
			bola.angulo *= -1;
		}
		// TODO: no momento a colisão com o piso está rebatendo a bola, mas
		// deverá contar como derrota
		if(bola.y - bola.raio <= 0 || bola.y + bola.raio >= canvas.height){
			bola.baixo = !bola.baixo;
		}
		// TODO: colisão blocos
		bola.x += bola.angulo;
	},

	desenharBloco : function(linha, coluna){
		// desenhar apenas blocos que não sejam null
		// (ou seja, não colidiram com a bola)
		// cada linha terá uma cor, como o Breakout de Atari
		game.bloco = game.blocos[linha][coluna];
		if(game.bloco != null) {
			// devemos adicionar um contorno?
			//context.strokeRect(bloco.x, bloco.y, bloco.w, bloco.h); //contorno
			if(linha == 0) game.context.fillStyle = "green";
			else if(linha == 1) game.context.fillStyle = "blue";
			else if(linha == 2) game.context.fillStyle = "red";
			else if(linha == 3) game.context.fillStyle = "yellow";
			else if(linha == 4) game.context.fillStyle = "orange";
			game.context.fillRect(game.bloco.x, game.bloco.y, game.bloco.w, game.bloco.h); //preenchimento
		}
	},

	atualizarTela : function(){
		// Limpeza da tela
		game.context.clearRect(0, 0, game.width, game.height);

		// Jogador
		game.context.fillStyle = "black";
		game.context.fillRect(jogador.x, jogador.y, jogador.w, jogador.h);

		// Bola
		game.context.fillStyle = "red";
		game.context.beginPath();
		game.context.arc(bola.x, bola.y, bola.raio, 0, Math.PI*2, true);
		game.context.closePath();
		game.context.fill();

		// Blocos - desenhados linha por linha
		for(var linha = 0; linha < game.numLinhas; linha++){
			for(var coluna = 0; coluna < game.numColunas; coluna++){
				game.desenharBloco(linha, coluna);
			}
		}
	}
};
<|endoftext|>


<commit_before>
require('coffee-script/register')
require('./index.coffee')
<commit_msg>added module.exports in runner<commit_after>
require('coffee-script/register')
module.exports = require('./index.coffee')
<|endoftext|>


<commit_before>
import { confirm } from 'src/util/ui';

import Status from './Status';
import processAction from './processAction';
import Roc from '../../rest-on-couch/Roc';

const muteSuccess = { muteSuccess: true };

export default class RequestManager {
  constructor(couchDB, options) {
    options = options || {};
    this.roc = new Roc({
      url: couchDB.url,
      database: couchDB.database,
    });
    this.sampleRoc = options.sampleRoc || null;
    this.servicesRoc = options.servicesRoc || null;
    this.servicesView = null;
  }

  getStatus(request) {
    return request.$content.status[0];
  }

  getStatusCode(request) {
    return Number(this.getStatus(request).status);
  }

  async cancel(request) {
    if (this.getStatusCode(request) === 0) {
      return null;
    }
    if (await confirm('cancel request?')) {
      return this.setStatus(request, 0);
    }
    return null;
  }

  async setStatus(request, status) {
    if (typeof status !== 'number') {
      status = Status.getStatusCode(status);
    }
    if (this.getStatusCode(request) === status) {
      return;
    }
    request.$content.status.unshift({
      date: Date.now(),
      status: status
    });
    await this.roc.update(request, muteSuccess);
  }
  async createCustomRequest(sample, options = {}) {
    let groups = options.groups || [];
    if (typeof groups === 'string') groups = [groups];

    const kind = options.kind || '';
    const data = options.data || {};
    const disableNotification = options.disableNotification;
    // allow each lab analytical lab to edit the entry
    for (const group of groups) {
      await this.sampleRoc.addGroup(sample, group, disableNotification);
    }
    // we can only add one request
    const requestObject = {
      $content: {
        productUuid: String(sample._id),
        productId: sample.$id,
        analysis: {
          kind,
          data
        },
        status: [
          {
            date: Date.now(),
            status: 10
          }
        ]
      },
      $owners: groups
    };

    return this.roc.create(requestObject, disableNotification);
  }

  async createRequests(sample, list) {
    const groups = Array.from(new Set(list.map((el) => el.kind)));
    // allow each lab to edit the entry
    for (const group of groups) {
      await this.sampleRoc.addGroup(sample, group, muteSuccess);
    }
    for (const req of list) {
      const requestObject = {
        $content: {
          productUuid: String(sample._id),
          productId: sample.$id,
          analysis: req,
          status: [
            {
              date: Date.now(),
              status: 10
            }
          ]
        },
        $owners: [req.kind]
      };
      await this.roc.create(requestObject, muteSuccess);
    }
  }

  async view(name, options) {
    const viewData = await this.roc.view(name, options);
    viewData.sort(sortRequestsByStatus);
    addChangeListener(viewData);
    return viewData;
  }

  async initServices(name, options) {
    if (this.servicesView) {
      return this.servicesView;
    } else {
      this.servicesView = this.servicesRoc.view(name, options);
      return this.servicesView;
    }
  }

  /**
   *
   * @param {string} scan : either the result of a scan or an uuid
   */
  async getRequest(scan) {
    let uuid = scan.replace(/[^a-z0-9:_]/g, '');
    if (uuid.includes('_')) {
      uuid = uuid.replace(/.*_r:([a-z0-9]*).*/, '$1');
    }
    if (uuid.length === 0) return;
    if (uuid.length !== 32) {
      return;
    }
    return await roc.document(uuid);
  }

  async getSample(request) {
    return this.sampleRoc.document(request.$content.productUuid);
  }

  async getPrintTemplate(analysis) {
    const experiment = await this.getExperiment(analysis);
    return experiment.twig;
  }

  async getExperiment(analysis) {
    const services = await this.initServices();
    const serviceId = analysis.kind;
    const instrument = analysis.instrument;
    const configName = analysis.configuration;
    return services
      .find((s) => String(s.$id) === String(serviceId)).$content.instruments
      .find((i) => String(i.name) === String(instrument)).experiments
      .find((e) => String(e.name) === String(configName));
  }

  async getUserInfo() {
    return this.roc.getUserInfo();
  }
}

RequestManager.prototype.processAction = processAction;

function addChangeListener(view) {
  const id = view.onChange((event, triggerId) => {
    if (triggerId !== id) {
      updateView(view, id);
    }
  });
  updateView(view, id);
}

function updateView(view, id) {
  view.forEach(updateRequest);
  view.triggerChange(false, id);
}

function updateRequest(request) {
  const lastStatus = request.$content.status[0];
  const status = Status.getStatus(lastStatus.status);
  request.statusText = status[0];
  request.statusColor = status[1];
}

function sortRequestsByStatus(req1, req2) {
  return req1.$content.status[0].status - req2.$content.status[0].status;
}
<commit_msg>fix getRequest<commit_after>
import { confirm } from 'src/util/ui';

import Status from './Status';
import processAction from './processAction';
import Roc from '../../rest-on-couch/Roc';

const muteSuccess = { muteSuccess: true };

export default class RequestManager {
  constructor(couchDB, options) {
    options = options || {};
    this.roc = new Roc({
      url: couchDB.url,
      database: couchDB.database,
    });
    this.sampleRoc = options.sampleRoc || null;
    this.servicesRoc = options.servicesRoc || null;
    this.servicesView = null;
  }

  getStatus(request) {
    return request.$content.status[0];
  }

  getStatusCode(request) {
    return Number(this.getStatus(request).status);
  }

  async cancel(request) {
    if (this.getStatusCode(request) === 0) {
      return null;
    }
    if (await confirm('cancel request?')) {
      return this.setStatus(request, 0);
    }
    return null;
  }

  async setStatus(request, status) {
    if (typeof status !== 'number') {
      status = Status.getStatusCode(status);
    }
    if (this.getStatusCode(request) === status) {
      return;
    }
    request.$content.status.unshift({
      date: Date.now(),
      status: status
    });
    await this.roc.update(request, muteSuccess);
  }
  async createCustomRequest(sample, options = {}) {
    let groups = options.groups || [];
    if (typeof groups === 'string') groups = [groups];

    const kind = options.kind || '';
    const data = options.data || {};
    const disableNotification = options.disableNotification;
    // allow each lab analytical lab to edit the entry
    for (const group of groups) {
      await this.sampleRoc.addGroup(sample, group, disableNotification);
    }
    // we can only add one request
    const requestObject = {
      $content: {
        productUuid: String(sample._id),
        productId: sample.$id,
        analysis: {
          kind,
          data
        },
        status: [
          {
            date: Date.now(),
            status: 10
          }
        ]
      },
      $owners: groups
    };

    return this.roc.create(requestObject, disableNotification);
  }

  async createRequests(sample, list) {
    const groups = Array.from(new Set(list.map((el) => el.kind)));
    // allow each lab to edit the entry
    for (const group of groups) {
      await this.sampleRoc.addGroup(sample, group, muteSuccess);
    }
    for (const req of list) {
      const requestObject = {
        $content: {
          productUuid: String(sample._id),
          productId: sample.$id,
          analysis: req,
          status: [
            {
              date: Date.now(),
              status: 10
            }
          ]
        },
        $owners: [req.kind]
      };
      await this.roc.create(requestObject, muteSuccess);
    }
  }

  async view(name, options) {
    const viewData = await this.roc.view(name, options);
    viewData.sort(sortRequestsByStatus);
    addChangeListener(viewData);
    return viewData;
  }

  async initServices(name, options) {
    if (this.servicesView) {
      return this.servicesView;
    } else {
      this.servicesView = this.servicesRoc.view(name, options);
      return this.servicesView;
    }
  }

  /**
   *
   * @param {string} scan : either the result of a scan or an uuid
   */
  async getRequest(scan) {
    let uuid = scan.replace(/[^a-z0-9:_]/g, '');
    if (uuid.includes('_')) {
      uuid = uuid.replace(/.*_r:([a-z0-9]*).*/, '$1');
    }
    if (uuid.length === 0) return;
    if (uuid.length !== 32) {
      return;
    }
    return await this.roc.document(uuid);
  }

  async getSample(request) {
    return this.sampleRoc.document(request.$content.productUuid);
  }

  async getPrintTemplate(analysis) {
    const experiment = await this.getExperiment(analysis);
    return experiment.twig;
  }

  async getExperiment(analysis) {
    const services = await this.initServices();
    const serviceId = analysis.kind;
    const instrument = analysis.instrument;
    const configName = analysis.configuration;
    return services
      .find((s) => String(s.$id) === String(serviceId)).$content.instruments
      .find((i) => String(i.name) === String(instrument)).experiments
      .find((e) => String(e.name) === String(configName));
  }

  async getUserInfo() {
    return this.roc.getUserInfo();
  }
}

RequestManager.prototype.processAction = processAction;

function addChangeListener(view) {
  const id = view.onChange((event, triggerId) => {
    if (triggerId !== id) {
      updateView(view, id);
    }
  });
  updateView(view, id);
}

function updateView(view, id) {
  view.forEach(updateRequest);
  view.triggerChange(false, id);
}

function updateRequest(request) {
  const lastStatus = request.$content.status[0];
  const status = Status.getStatus(lastStatus.status);
  request.statusText = status[0];
  request.statusColor = status[1];
}

function sortRequestsByStatus(req1, req2) {
  return req1.$content.status[0].status - req2.$content.status[0].status;
}
<|endoftext|>


<commit_before>'use strict';

import {confirm} from 'src/util/ui';

const statuses = {
    // statusCode: [statusName, statusColor]
    0: ['Cancelled', '#AAAAAA'],
    10: ['Pending', '#FFDC00'],
    20: ['Processing', '#0074D9'],
    30: ['Finished', '#01FF70'],
    90: ['Error', '#FF4136']
};
const muteSuccess = {muteSuccess: true};

export function getStatus(code) {
    if (statuses[code]) {
        return statuses[code];
    }
    throw new Error(`No such status: ${code}`);
}

export function getStatusName(code) {
    return getStatus(code)[0];
}

export function getStatusColor(code) {
    return getStatus(code)[1];
}

export function getStatusCode(name) {
    for (const status in statuses) {
        if (statuses[status][0] === name) {
            return Number(status);
        }
    }
    throw new Error(`No such status: ${name}`);
}

export default class RequestManager {
    constructor(roc, options) {
        options = options || {};
        this.roc = roc;
        this.sampleRoc = options.sampleRoc || null;
        this.servicesRoc = options.servicesRoc || null;
        this.servicesView = null;
    }

    getStatus(request) {
        return request.$content.status[0];
    }

    getStatusCode(request) {
        return Number(this.getStatus(request).status);
    }

    getLastStatus(request) {
        return request.$content.status[request.$content.status.length - 1];
    }

    async cancel(request) {
        if (this.getStatusCode(request) === 0) {
            return;
        }
        if (await confirm('cancel request?')) {
            return this.setStatus(request, 0);
        }
    }

    async setStatus(request, status) {
        if (typeof status !== 'number') {
            status = getStatusCode(status);
        }
        if (this.getStatusCode(request) === status) {
            return;
        }
        request.$content.status.unshift({
            date: Date.now(),
            status: status
        });
        await this.roc.update(request, muteSuccess);
    }
   async createCustomRequest(sample, options = {}) {
        const groups = options.groups || [];
        if (typeof groups === 'string') groups=[groups];

        const kind = options.kind || '';
        const data = options.data || {};
        const disableNotification = options.disableNotification;
        // allow each lab analytical lab to edit the entry
        for (const group of groups) {
            await this.sampleRoc.addGroup(sample, group, disableNotification);
        }
        // we can only add one request
        const requestObject = {
            $content: {
                productUuid: String(sample._id),
                productId: sample.$id,
                analysis: {
                    kind,
                    data
                },
                status: [{
                    date: Date.now(),
                    status: 10
                }]
            },
            $owners: groups
        };
        return await this.roc.create(requestObject, disableNotification);
    }

    async createRequests(sample, list) {
        const groups = Array.from(new Set(list.map(el => el.kind)));
        // allow each lab to edit the entry
        for (const group of groups) {
            await this.sampleRoc.addGroup(sample, group, muteSuccess);
        }
        for (const req of list) {
            const requestObject = {
                $content: {
                    productUuid: String(sample._id),
                    productId: sample.$id,
                    analysis: req,
                    status: [{
                        date: Date.now(),
                        status: 10
                    }]
                },
                $owners: [req.kind]
            };
            await this.roc.create(requestObject, muteSuccess);
        }
    }

    async view(name, options) {
        const viewData = await this.roc.view(name, options);
        viewData.sort(sortRequestsByStatus);
        addChangeListener(viewData);
        return viewData;
    }

    async initServices(name, options) {
        if (this.servicesView) {
            return this.servicesView;
        } else {
            return this.servicesView = this.servicesRoc.view(name, options);
        }
    }

    async getSample(request) {
        return this.sampleRoc.document(request.$content.productUuid);
    }

    async getPrintTemplate(analysis) {
        const experiment = await this.getExperiment(analysis);
        return experiment.twig;
    }

    async getExperiment(analysis) {
        const services = await this.initServices();
        const serviceId = analysis.kind;
        const instrument = analysis.instrument;
        const configName = analysis.configuration;
        return services
            .find(s => String(s.$id) === String(serviceId)).$content.instruments
            .find(i => String(i.name) === String(instrument)).experiments
            .find(e => String(e.name) === String(configName));
    }

    async getUserInfo() {
        return this.roc.getUserInfo();
    }
}

function addChangeListener(view) {
    const id = view.onChange((event, triggerId) => {
        if (triggerId !== id) {
            updateView(view, id);
        }
    });
    updateView(view, id);
}

function updateView(view, id) {
    view.forEach(updateRequest);
    view.triggerChange(false, id);
}

function updateRequest(request) {
    const lastStatus = request.$content.status[0];
    const status = getStatus(lastStatus.status);
    request.statusText = status[0];
    request.statusColor = status[1];
}

function sortRequestsByStatus(req1, req2) {
    return req1.$content.status[0].status - req2.$content.status[0].status;
}
<commit_msg>Remove await in createCustomRequest<commit_after>'use strict';

import {confirm} from 'src/util/ui';

const statuses = {
    // statusCode: [statusName, statusColor]
    0: ['Cancelled', '#AAAAAA'],
    10: ['Pending', '#FFDC00'],
    20: ['Processing', '#0074D9'],
    30: ['Finished', '#01FF70'],
    90: ['Error', '#FF4136']
};
const muteSuccess = {muteSuccess: true};

export function getStatus(code) {
    if (statuses[code]) {
        return statuses[code];
    }
    throw new Error(`No such status: ${code}`);
}

export function getStatusName(code) {
    return getStatus(code)[0];
}

export function getStatusColor(code) {
    return getStatus(code)[1];
}

export function getStatusCode(name) {
    for (const status in statuses) {
        if (statuses[status][0] === name) {
            return Number(status);
        }
    }
    throw new Error(`No such status: ${name}`);
}

export default class RequestManager {
    constructor(roc, options) {
        options = options || {};
        this.roc = roc;
        this.sampleRoc = options.sampleRoc || null;
        this.servicesRoc = options.servicesRoc || null;
        this.servicesView = null;
    }

    getStatus(request) {
        return request.$content.status[0];
    }

    getStatusCode(request) {
        return Number(this.getStatus(request).status);
    }

    getLastStatus(request) {
        return request.$content.status[request.$content.status.length - 1];
    }

    async cancel(request) {
        if (this.getStatusCode(request) === 0) {
            return;
        }
        if (await confirm('cancel request?')) {
            return this.setStatus(request, 0);
        }
    }

    async setStatus(request, status) {
        if (typeof status !== 'number') {
            status = getStatusCode(status);
        }
        if (this.getStatusCode(request) === status) {
            return;
        }
        request.$content.status.unshift({
            date: Date.now(),
            status: status
        });
        await this.roc.update(request, muteSuccess);
    }
   async createCustomRequest(sample, options = {}) {
        const groups = options.groups || [];
        if (typeof groups === 'string') groups=[groups];

        const kind = options.kind || '';
        const data = options.data || {};
        const disableNotification = options.disableNotification;
        // allow each lab analytical lab to edit the entry
        for (const group of groups) {
            await this.sampleRoc.addGroup(sample, group, disableNotification);
        }
        // we can only add one request
        const requestObject = {
            $content: {
                productUuid: String(sample._id),
                productId: sample.$id,
                analysis: {
                    kind,
                    data
                },
                status: [{
                    date: Date.now(),
                    status: 10
                }]
            },
            $owners: groups
        };
        return this.roc.create(requestObject, disableNotification);
    }

    async createRequests(sample, list) {
        const groups = Array.from(new Set(list.map(el => el.kind)));
        // allow each lab to edit the entry
        for (const group of groups) {
            await this.sampleRoc.addGroup(sample, group, muteSuccess);
        }
        for (const req of list) {
            const requestObject = {
                $content: {
                    productUuid: String(sample._id),
                    productId: sample.$id,
                    analysis: req,
                    status: [{
                        date: Date.now(),
                        status: 10
                    }]
                },
                $owners: [req.kind]
            };
            await this.roc.create(requestObject, muteSuccess);
        }
    }

    async view(name, options) {
        const viewData = await this.roc.view(name, options);
        viewData.sort(sortRequestsByStatus);
        addChangeListener(viewData);
        return viewData;
    }

    async initServices(name, options) {
        if (this.servicesView) {
            return this.servicesView;
        } else {
            return this.servicesView = this.servicesRoc.view(name, options);
        }
    }

    async getSample(request) {
        return this.sampleRoc.document(request.$content.productUuid);
    }

    async getPrintTemplate(analysis) {
        const experiment = await this.getExperiment(analysis);
        return experiment.twig;
    }

    async getExperiment(analysis) {
        const services = await this.initServices();
        const serviceId = analysis.kind;
        const instrument = analysis.instrument;
        const configName = analysis.configuration;
        return services
            .find(s => String(s.$id) === String(serviceId)).$content.instruments
            .find(i => String(i.name) === String(instrument)).experiments
            .find(e => String(e.name) === String(configName));
    }

    async getUserInfo() {
        return this.roc.getUserInfo();
    }
}

function addChangeListener(view) {
    const id = view.onChange((event, triggerId) => {
        if (triggerId !== id) {
            updateView(view, id);
        }
    });
    updateView(view, id);
}

function updateView(view, id) {
    view.forEach(updateRequest);
    view.triggerChange(false, id);
}

function updateRequest(request) {
    const lastStatus = request.$content.status[0];
    const status = getStatus(lastStatus.status);
    request.statusText = status[0];
    request.statusColor = status[1];
}

function sortRequestsByStatus(req1, req2) {
    return req1.$content.status[0].status - req2.$content.status[0].status;
}
<|endoftext|>


<commit_before>/*******************************************************************************
  BASE REQUIREMENTS
*******************************************************************************/

var is_meteor = 'undefined' != typeof(Meteor);
var require   = (is_meteor ? Npm.require : require);

require('shelljs/global');

var fs    = require('fs');
var path  = require('path');
var os    = require('os');
var spawn = require('child_process').spawn;

var join = path.join;

/*******************************************************************************
  VARIABLES
*******************************************************************************/

// current version
VERSION = '1.1.3';

// system
_PLATFORM = process.platform;
_ARCH     = process.arch;
_OS       = (_PLATFORM === 'darwin' ? 'osx' : _PLATFORM);
_OS_HOME  = (_PLATFORM == 'win32' ? 'USERPROFILE' : 'HOME');
_TMP      = os.tmpdir();

// silent execs
_SILENT_EXECS = {silent: process.env.BUTTER !== 'true'};

// general folders
_HOME        = process.env[_OS_HOME];
_APP_ROOT    = path.resolve(is_meteor ? '../../../../..' : '.');

// elecritifed lots of folders
_ELECTRIFIED          = join(_APP_ROOT             , '.electrify');
_ELECTRIFIED_RELEASE  = join(_ELECTRIFIED          , '.dist');
_ELECTRIFIED_INDEX    = join(_ELECTRIFIED          , 'index.js');
_ELECTRIFIED_PKG      = join(_ELECTRIFIED          , 'package.json');
_ELECTRIFIED_IGNORE   = join(_ELECTRIFIED          , '.gitignore');
_ELECTRIFIED_TEMP     = join(_ELECTRIFIED          , 'temp');
_ELECTRIFIED_APP      = join(_ELECTRIFIED          , 'app');
_ELECTRIFIED_BIN      = join(_ELECTRIFIED          , 'bin');

// electrified mongo
_ELECTRIFIED_DB       = join(_ELECTRIFIED          , 'db');
_ELECTRIFIED_BUNDLE   = join(_ELECTRIFIED_TEMP     , 'bundle');
_ELECTRIFIED_SERVER   = join(_ELECTRIFIED_APP      , 'programs', 'server');

// electrified node_modules
_ELECTRIFIED_MODS     = join(_ELECTRIFIED          , 'node_modules');
_ELECTRIFIED_ELECTRON = join(_ELECTRIFIED_MODS     , 'electron-prebuilt');
_ELECTRIFIED_PACKAGER = join(_ELECTRIFIED_MODS     , 'electron-packager');

_ELECTRON_PKG_JSON    = join(_ELECTRIFIED_ELECTRON , 'package.json');

_ELECTRIFIED_MODS_B   = join(_ELECTRIFIED_MODS     , '.bin');
_ELECTRON             = join(_ELECTRIFIED_MODS_B   , 'electron');
_ELECTRON_PACKAGER    = join(_ELECTRIFIED_MODS_B   , 'electron-packager');

// meteor stuff
if(_PLATFORM === 'win32') {

  _METEOR_FOLDER  = path.join(_HOME, 'AppData', 'Local', '.meteor');
  _METEOR_SYMLINK = cat(path.join(_METEOR_FOLDER, 'meteor.bat'));
  _METEOR_SYMLINK = _METEOR_SYMLINK.match(/\\packages.*\\meteor\.bat/)[0];
  _METEOR_BIN     = path.join(_METEOR_FOLDER, _METEOR_SYMLINK);

} else {

  _METEOR_FOLDER  = path.join(_HOME, '.meteor');
  _METEOR_SYMLINK = fs.readlinkSync(path.join(_METEOR_FOLDER, 'meteor'));
  _METEOR_BIN     = path.join(_METEOR_FOLDER, _METEOR_SYMLINK);

}

// meteor folders
_METEOR_TOOLS_FOLDER    = _METEOR_BIN.replace(/meteor(\.bat)?$/m, '');
_METEOR_DEV_BUNDLE      = path.join(_METEOR_TOOLS_FOLDER, 'dev_bundle');
_METEOR_SERVER_LIB      = path.join(_METEOR_DEV_BUNDLE, 'server-lib');
_METEOR_SERVER_LIB_MODS = path.join(_METEOR_SERVER_LIB, 'node_modules');

// meteor's mongodb distro
_METEOR_MONGO  = path.join(_METEOR_DEV_BUNDLE, 'mongodb', 'bin', 'mongo');
_METEOR_MONGOD = _METEOR_MONGO + 'd';

// meteor's nodejs + npm distro
_METEOR_NODE = path.join(_METEOR_DEV_BUNDLE, 'bin', 'node');
_METEOR_NPM  = path.join(_METEOR_DEV_BUNDLE, 'bin', 'npm');

_METEOR_LOCAL_DB = path.join(_APP_ROOT, '.meteor', 'local', 'db');

if(is_meteor)
  _METEOR_SETTINGS = Meteor.settings;
else {
  _METEOR_SETTINGS = _NPM_SETTINGS;
}

// bin ref differences
if(_PLATFORM === 'win32') {

  _ELECTRON          += '.cmd';
  _ELECTRON_PACKAGER += '.cmd';

  _METEOR_MONGO  += '.exe';
  _METEOR_MONGOD += '.exe';

  _METEOR_NODE   += '.exe';

  _METEOR_NPM    += '.cmd';
}

// database
_METEOR_LOCAL_DB_JOURNAL = path.join(_METEOR_LOCAL_DB, 'journal');
_METEOR_LOCAL_DB_FILES   = [
  path.join(_METEOR_LOCAL_DB, 'local.ns'),
  path.join(_METEOR_LOCAL_DB, 'local.0'),
  path.join(_METEOR_LOCAL_DB, 'local.1'),
  path.join(_METEOR_LOCAL_DB, 'meteor.ns'),
  path.join(_METEOR_LOCAL_DB, 'meteor.0'),
  path.join(_METEOR_LOCAL_DB, 'meteor.1')
];

/*******************************************************************************
  INITIALIZATION
*******************************************************************************/

this.__defineGetter__('electrify', release);

if(is_meteor) {
  // in development mode this method is called everytime server is restarted due
  // to some server file change, so we need some trick to avoid opening a new
  // Electron window everytime it happens - see the ppid stuff below
  Meteor.startup(function(){

    // ELECTRON_PRODUCTION variable comes from the `index.js` file, check its
    // initialization section to get wtf is going on here - basically, since the
    // app has bem packaged, this whole method is useless, so we simply abort
    if(process.env.ELECTRON_PRODUCTION) return;

    // now we fetch the meteor previous and current PID in order to compare them
    var ppid_filepath = path.join(_TMP, '.electrify-ppid')
    var previous_ppid = read(ppid_filepath);
    var current_ppid  = process.env.METEOR_PARENT_PID;

    // if the current ppid differs from the last, we assume some SIGINT has
    // ocurred on the meteor-tool command line, so it's fine to launch electron
    // window again otherwise we simply assume it's a mere server restart, close
    // our eyes and relax :)
    if(previous_ppid != current_ppid) {

      // we also save the current ppid for next comparison
      write(ppid_filepath, current_ppid);

      // and start everything for development
      development();
    }
  });
}

/*******************************************************************************
  STARTUP MODES
*******************************************************************************/

if(!is_meteor) exports.release = release;

function development(){
  setup_folders();
  copy_templates();
  install_electrified_dependencies();
  launch_electron();
}

function release(){
  setup_folders();
  copy_templates();
  copy_bins();
  // copy_database();
  bundle_meteor();
  install_meteor_dependencies();
  install_electrified_dependencies();
  package_app();
}

/*******************************************************************************
  FOLDERS
*******************************************************************************/

function setup_folders(){
  if(!exists(_ELECTRIFIED)) {
    log('setting up folders');
    mkdir('-p', _ELECTRIFIED);
  }
}

/*******************************************************************************
  TEMPLATES
*******************************************************************************/

function copy_templates() {
  if(!exists(_ELECTRIFIED_INDEX))
    log('copying template files');

  if(!exists(_ELECTRIFIED_PKG))
    write(_ELECTRIFIED_PKG, TEMPLATES.pkg);

  if(!exists(_ELECTRIFIED_INDEX))
    write(_ELECTRIFIED_INDEX, TEMPLATES.index);

  if(!exists(_ELECTRIFIED_IGNORE))
    write(_ELECTRIFIED_IGNORE, TEMPLATES.gitignore);
}

/*******************************************************************************
  INSTALL DEPENDENCIES
*******************************************************************************/

function install_electrified_dependencies() {

  // in dev mode, link local copy of electrify module :)
  if(process.env.BUTTER === 'true') {

    var source  = path.join(_APP_ROOT, '..', 'electrify');
    var symlink =  path.join(_ELECTRIFIED_MODS, 'electrify');

    if(!exists(_ELECTRIFIED_MODS))
      mkdir('-p', _ELECTRIFIED_MODS);

    // when in dev mode, link electrify npm package right into node_modules
    rm('-rf', symlink);
    ln('-s', source, symlink);
  }

  // checks for a possible previous corrupted install of modules, this can
  // happen if the network fails during the `npm_install` command or other
  // similar case that precociously interupt it, preventing it from downloading
  // and building electron-prebuilt or electron-packager modules, gracefully

  // first we check if `node_modules` folder exists
  if(exists(_ELECTRIFIED_MODS)) {

    // then we check if some of the needed binaries is not present
    var binaries_missing = !exists(_ELECTRON) || !exists(_ELECTRON_PACKAGER);

    // or local npm version is out of date
    var package_json = require(_ELECTRIFIED_PKG)
    var outdated = package_json.devDependencies.electrify != VERSION;

    if(binaries_missing || outdated) {
      // in case something is missing, it means something went wrong during the
      // `npm_install` command, preventing it from finishing downloading or
      // building everything properly

      // so we remove the `node_modules` folder to erase any trace of these
      // corrupted/incomplete install process before procceding and installing
      // everything again
      rm('-rf', _ELECTRIFIED_MODS);

      // update local copy of npm electify module
      if(outdated) {
        log('upgrading local `.electrify` npm module to `v'+ VERSION +'`');
        package_json.devDependencies.electrify = VERSION;
        write(_ELECTRIFIED_PKG, JSON.stringify(package_json, null, 2));
      }
    }
  }

  log('installing electrified dependencies');
  exec('cd ' + _ELECTRIFIED + ' && '+ _METEOR_NPM +' install', _SILENT_EXECS);
}

/*******************************************************************************
  ELECTRON LAUNCHER
*******************************************************************************/

function launch_electron() {
  log('launching electron');
  spawn(_ELECTRON, [_ELECTRIFIED]);
}

/*******************************************************************************
  INJECTIONS
*******************************************************************************/

function copy_bins() {
  if(!exists(_ELECTRIFIED_BIN)){
    mkdir('-p', _ELECTRIFIED_BIN);
    log('copying mongo and node binaries');
    cp(_METEOR_MONGO, _ELECTRIFIED_BIN);
    cp(_METEOR_MONGOD, _ELECTRIFIED_BIN);
    cp(_METEOR_NODE, _ELECTRIFIED_BIN);
  }
}

function copy_database() {
  log('copying database');
  rm('-rf', _ELECTRIFIED_DB);
  mkdir('-p', _ELECTRIFIED_DB);
  cp('-r', _METEOR_LOCAL_DB_JOURNAL, _ELECTRIFIED_DB);
  _METEOR_LOCAL_DB_FILES.forEach(function(filepath){
    if(exists(filepath))
      cp('-r', filepath, _ELECTRIFIED_DB);
  });
}

/*******************************************************************************
  BUNDLE METEOR APP
*******************************************************************************/

function bundle_meteor() {
  log('bundling meteor');

  exec([
    'cd ' + _APP_ROOT,
    'meteor build ' + _ELECTRIFIED_TEMP + ' --server null --directory',
    'cd ' + _APP_ROOT
  ].join(' && '), _SILENT_EXECS);

  rm('-rf', _ELECTRIFIED_APP);
  mv(_ELECTRIFIED_BUNDLE, _ELECTRIFIED_APP);
  rm('-rf', _ELECTRIFIED_TEMP);
}

function install_meteor_dependencies() {
  log('installing meteor dependencies');
  // instead of `npm install` :
  //
  //     exec('cd ' + _ELCTRIFIED_SERVER + ' && npm install', _SILENT_EXECS);
  //
  // which would imply in another bugs around node-fibers native re-build with
  // node-gyp, we just copy the whole `node_modules` folder that is officially
  // distributed with meteor, its 'ready to go and doesn't need to be rebuilt
  cp('-r', _METEOR_SERVER_LIB_MODS, _ELECTRIFIED_SERVER);
}

/*******************************************************************************
  PACKAGE APP
*******************************************************************************/

function package_app() {
  var name    = require(_ELECTRIFIED_PKG).name;
  var version = require(_ELECTRON_PKG_JSON).version;

  var command = [
    _ELECTRON_PACKAGER +' '+ _ELECTRIFIED + ' ' + name,
    '--out=' + _ELECTRIFIED_RELEASE,
    '--arch=' + _ARCH,
    '--platform=' + _PLATFORM,
    '--version='+ version
  ].join(' ');

  log(
    'packaging app for platform '+ _PLATFORM +
    ' '+ _ARCH + ' using electron v' + version
  );

  rm('-rf', _ELECTRIFIED_RELEASE);
  mkdir('-p', _ELECTRIFIED_RELEASE);

  exec(command, _SILENT_EXECS);

  log('wrote new app to ', _ELECTRIFIED_RELEASE);
}

/*******************************************************************************
  TEMPLATES
*******************************************************************************/

TEMPLATES = {
  index: [
    "var app       = require('app');",
    "var browser   = require('browser-window');",
    "var electrify = require('electrify');",
    "",
    "",
    "var window    = null;",
    "",
    "app.on('ready', function() {",
    "",
    "  window = new browser({",
    "    width: 1200,",
    "    height: 900,",
    "    'node-integration': false",
    "  });",
    "  ",
    "  electrify.boot(function() {",
    "    window.loadUrl(electrify.meteor_url);",
    "  });",
    "",
    "});",
    "",
    "",
    "app.on('will-quit', function(event) {",
    "  electrify.shutdown(app, event);",
    "});",
    "",
    "app.on('window-all-closed', function() {",
    "  app.quit();",
    "});"
  ].join('\r\n'),

  pkg: [
  '{',
  '  "name": "Electrified",',
  '  "main": "index.js",',
  '  "devDependencies": {',
  '    "electron-prebuilt": "^0.29.2",',
  '    "electron-packager": "^5.0.1",',
  '    "electrify": "'+ VERSION +'"',
  '  }',
  '}'
  ].join('\r\n'),

  gitignore: [
    '.DS_Store',
    '.dist',
    'app',
    'bin',
    'db',
    'node_modules'
  ].join('\r\n')
};

/*******************************************************************************
  SIMPLE FILE READ / WRITE / EXISTS helpers
*******************************************************************************/

function read(filepath, encoding) {
  if(exists(filepath))
    return fs.readFileSync(filepath, encoding || 'utf8');
}

function write(filepath, contents) {
  fs.writeFileSync(filepath, contents);
}

function exists(filepath) {
  return fs.existsSync(filepath);
}

/*******************************************************************************
  DEBUG
*******************************************************************************/

function log(){
  var args = Array.prototype.slice.call(arguments);
  console.log.apply(null, ['electrify: '].concat(args));
}<commit_msg>abort `npm install` if everything is okay<commit_after>/*******************************************************************************
  BASE REQUIREMENTS
*******************************************************************************/

var is_meteor = 'undefined' != typeof(Meteor);
var require   = (is_meteor ? Npm.require : require);

require('shelljs/global');

var fs    = require('fs');
var path  = require('path');
var os    = require('os');
var spawn = require('child_process').spawn;

var join = path.join;

/*******************************************************************************
  VARIABLES
*******************************************************************************/

// current version
VERSION = '1.1.3';

// system
_PLATFORM = process.platform;
_ARCH     = process.arch;
_OS       = (_PLATFORM === 'darwin' ? 'osx' : _PLATFORM);
_OS_HOME  = (_PLATFORM == 'win32' ? 'USERPROFILE' : 'HOME');
_TMP      = os.tmpdir();

// silent execs
_SILENT_EXECS = {silent: process.env.BUTTER !== 'true'};

// general folders
_HOME        = process.env[_OS_HOME];
_APP_ROOT    = path.resolve(is_meteor ? '../../../../..' : '.');

// elecritifed lots of folders
_ELECTRIFIED          = join(_APP_ROOT             , '.electrify');
_ELECTRIFIED_RELEASE  = join(_ELECTRIFIED          , '.dist');
_ELECTRIFIED_INDEX    = join(_ELECTRIFIED          , 'index.js');
_ELECTRIFIED_PKG      = join(_ELECTRIFIED          , 'package.json');
_ELECTRIFIED_IGNORE   = join(_ELECTRIFIED          , '.gitignore');
_ELECTRIFIED_TEMP     = join(_ELECTRIFIED          , 'temp');
_ELECTRIFIED_APP      = join(_ELECTRIFIED          , 'app');
_ELECTRIFIED_BIN      = join(_ELECTRIFIED          , 'bin');

// electrified mongo
_ELECTRIFIED_DB       = join(_ELECTRIFIED          , 'db');
_ELECTRIFIED_BUNDLE   = join(_ELECTRIFIED_TEMP     , 'bundle');
_ELECTRIFIED_SERVER   = join(_ELECTRIFIED_APP      , 'programs', 'server');

// electrified node_modules
_ELECTRIFIED_MODS     = join(_ELECTRIFIED          , 'node_modules');
_ELECTRIFIED_ELECTRON = join(_ELECTRIFIED_MODS     , 'electron-prebuilt');
_ELECTRIFIED_PACKAGER = join(_ELECTRIFIED_MODS     , 'electron-packager');

_ELECTRON_PKG_JSON    = join(_ELECTRIFIED_ELECTRON , 'package.json');

_ELECTRIFIED_MODS_B   = join(_ELECTRIFIED_MODS     , '.bin');
_ELECTRON             = join(_ELECTRIFIED_MODS_B   , 'electron');
_ELECTRON_PACKAGER    = join(_ELECTRIFIED_MODS_B   , 'electron-packager');

// meteor stuff
if(_PLATFORM === 'win32') {

  _METEOR_FOLDER  = path.join(_HOME, 'AppData', 'Local', '.meteor');
  _METEOR_SYMLINK = cat(path.join(_METEOR_FOLDER, 'meteor.bat'));
  _METEOR_SYMLINK = _METEOR_SYMLINK.match(/\\packages.*\\meteor\.bat/)[0];
  _METEOR_BIN     = path.join(_METEOR_FOLDER, _METEOR_SYMLINK);

} else {

  _METEOR_FOLDER  = path.join(_HOME, '.meteor');
  _METEOR_SYMLINK = fs.readlinkSync(path.join(_METEOR_FOLDER, 'meteor'));
  _METEOR_BIN     = path.join(_METEOR_FOLDER, _METEOR_SYMLINK);

}

// meteor folders
_METEOR_TOOLS_FOLDER    = _METEOR_BIN.replace(/meteor(\.bat)?$/m, '');
_METEOR_DEV_BUNDLE      = path.join(_METEOR_TOOLS_FOLDER, 'dev_bundle');
_METEOR_SERVER_LIB      = path.join(_METEOR_DEV_BUNDLE, 'server-lib');
_METEOR_SERVER_LIB_MODS = path.join(_METEOR_SERVER_LIB, 'node_modules');

// meteor's mongodb distro
_METEOR_MONGO  = path.join(_METEOR_DEV_BUNDLE, 'mongodb', 'bin', 'mongo');
_METEOR_MONGOD = _METEOR_MONGO + 'd';

// meteor's nodejs + npm distro
_METEOR_NODE = path.join(_METEOR_DEV_BUNDLE, 'bin', 'node');
_METEOR_NPM  = path.join(_METEOR_DEV_BUNDLE, 'bin', 'npm');

_METEOR_LOCAL_DB = path.join(_APP_ROOT, '.meteor', 'local', 'db');

if(is_meteor)
  _METEOR_SETTINGS = Meteor.settings;
else {
  _METEOR_SETTINGS = _NPM_SETTINGS;
}

// bin ref differences
if(_PLATFORM === 'win32') {

  _ELECTRON          += '.cmd';
  _ELECTRON_PACKAGER += '.cmd';

  _METEOR_MONGO  += '.exe';
  _METEOR_MONGOD += '.exe';

  _METEOR_NODE   += '.exe';

  _METEOR_NPM    += '.cmd';
}

// database
_METEOR_LOCAL_DB_JOURNAL = path.join(_METEOR_LOCAL_DB, 'journal');
_METEOR_LOCAL_DB_FILES   = [
  path.join(_METEOR_LOCAL_DB, 'local.ns'),
  path.join(_METEOR_LOCAL_DB, 'local.0'),
  path.join(_METEOR_LOCAL_DB, 'local.1'),
  path.join(_METEOR_LOCAL_DB, 'meteor.ns'),
  path.join(_METEOR_LOCAL_DB, 'meteor.0'),
  path.join(_METEOR_LOCAL_DB, 'meteor.1')
];

/*******************************************************************************
  INITIALIZATION
*******************************************************************************/

this.__defineGetter__('electrify', release);

if(is_meteor) {
  // in development mode this method is called everytime server is restarted due
  // to some server file change, so we need some trick to avoid opening a new
  // Electron window everytime it happens - see the ppid stuff below
  Meteor.startup(function(){

    // ELECTRON_PRODUCTION variable comes from the `index.js` file, check its
    // initialization section to get wtf is going on here - basically, since the
    // app has bem packaged, this whole method is useless, so we simply abort
    if(process.env.ELECTRON_PRODUCTION) return;

    // now we fetch the meteor previous and current PID in order to compare them
    var ppid_filepath = path.join(_TMP, '.electrify-ppid')
    var previous_ppid = read(ppid_filepath);
    var current_ppid  = process.env.METEOR_PARENT_PID;

    // if the current ppid differs from the last, we assume some SIGINT has
    // ocurred on the meteor-tool command line, so it's fine to launch electron
    // window again otherwise we simply assume it's a mere server restart, close
    // our eyes and relax :)
    if(previous_ppid != current_ppid) {

      // we also save the current ppid for next comparison
      write(ppid_filepath, current_ppid);

      // and start everything for development
      development();
    }
  });
}

/*******************************************************************************
  STARTUP MODES
*******************************************************************************/

if(!is_meteor) exports.release = release;

function development(){
  setup_folders();
  copy_templates();
  install_electrified_dependencies();
  launch_electron();
}

function release(){
  setup_folders();
  copy_templates();
  copy_bins();
  // copy_database();
  bundle_meteor();
  install_meteor_dependencies();
  install_electrified_dependencies();
  package_app();
}

/*******************************************************************************
  FOLDERS
*******************************************************************************/

function setup_folders(){
  if(!exists(_ELECTRIFIED)) {
    log('setting up folders');
    mkdir('-p', _ELECTRIFIED);
  }
}

/*******************************************************************************
  TEMPLATES
*******************************************************************************/

function copy_templates() {
  if(!exists(_ELECTRIFIED_INDEX))
    log('copying template files');

  if(!exists(_ELECTRIFIED_PKG))
    write(_ELECTRIFIED_PKG, TEMPLATES.pkg);

  if(!exists(_ELECTRIFIED_INDEX))
    write(_ELECTRIFIED_INDEX, TEMPLATES.index);

  if(!exists(_ELECTRIFIED_IGNORE))
    write(_ELECTRIFIED_IGNORE, TEMPLATES.gitignore);
}

/*******************************************************************************
  INSTALL DEPENDENCIES
*******************************************************************************/

function install_electrified_dependencies() {

  // in dev mode, link local copy of electrify module :)
  if(process.env.BUTTER === 'true') {

    var source  = path.join(_APP_ROOT, '..', 'electrify');
    var symlink =  path.join(_ELECTRIFIED_MODS, 'electrify');

    if(!exists(_ELECTRIFIED_MODS))
      mkdir('-p', _ELECTRIFIED_MODS);

    // when in dev mode, link electrify npm package right into node_modules
    rm('-rf', symlink);
    ln('-s', source, symlink);
  }

  // checks for a possible previous corrupted install of modules, this can
  // happen if the network fails during the `npm_install` command or other
  // similar case that precociously interupt it, preventing it from downloading
  // and building electron-prebuilt or electron-packager modules, gracefully

  // first we check if `node_modules` folder exists
  if(exists(_ELECTRIFIED_MODS)) {

    // then we check if some of the needed binaries is not present
    var binaries_missing = !exists(_ELECTRON) || !exists(_ELECTRON_PACKAGER);

    // or local npm version is out of date
    var package_json = require(_ELECTRIFIED_PKG)
    var outdated = package_json.devDependencies.electrify != VERSION;

    if(binaries_missing || outdated) {
      // in case something is missing, it means something went wrong during the
      // `npm_install` command, preventing it from finishing downloading or
      // building everything properly

      // so we remove the `node_modules` folder to erase any trace of these
      // corrupted/incomplete install process before procceding and installing
      // everything again
      rm('-rf', _ELECTRIFIED_MODS);

      // update local copy of npm electify module
      if(outdated) {
        log('upgrading local `.electrify` npm module to `v'+ VERSION +'`');
        package_json.devDependencies.electrify = VERSION;
        write(_ELECTRIFIED_PKG, JSON.stringify(package_json, null, 2));
      }
    } else {
      return;
    }
  }

  log('installing electrified dependencies');
  exec('cd ' + _ELECTRIFIED + ' && '+ _METEOR_NPM +' install', _SILENT_EXECS);
}

/*******************************************************************************
  ELECTRON LAUNCHER
*******************************************************************************/

function launch_electron() {
  log('launching electron');
  spawn(_ELECTRON, [_ELECTRIFIED]);
}

/*******************************************************************************
  INJECTIONS
*******************************************************************************/

function copy_bins() {
  if(!exists(_ELECTRIFIED_BIN)){
    mkdir('-p', _ELECTRIFIED_BIN);
    log('copying mongo and node binaries');
    cp(_METEOR_MONGO, _ELECTRIFIED_BIN);
    cp(_METEOR_MONGOD, _ELECTRIFIED_BIN);
    cp(_METEOR_NODE, _ELECTRIFIED_BIN);
  }
}

function copy_database() {
  log('copying database');
  rm('-rf', _ELECTRIFIED_DB);
  mkdir('-p', _ELECTRIFIED_DB);
  cp('-r', _METEOR_LOCAL_DB_JOURNAL, _ELECTRIFIED_DB);
  _METEOR_LOCAL_DB_FILES.forEach(function(filepath){
    if(exists(filepath))
      cp('-r', filepath, _ELECTRIFIED_DB);
  });
}

/*******************************************************************************
  BUNDLE METEOR APP
*******************************************************************************/

function bundle_meteor() {
  log('bundling meteor');

  exec([
    'cd ' + _APP_ROOT,
    'meteor build ' + _ELECTRIFIED_TEMP + ' --server null --directory',
    'cd ' + _APP_ROOT
  ].join(' && '), _SILENT_EXECS);

  rm('-rf', _ELECTRIFIED_APP);
  mv(_ELECTRIFIED_BUNDLE, _ELECTRIFIED_APP);
  rm('-rf', _ELECTRIFIED_TEMP);
}

function install_meteor_dependencies() {
  log('installing meteor dependencies');
  // instead of `npm install` :
  //
  //     exec('cd ' + _ELCTRIFIED_SERVER + ' && npm install', _SILENT_EXECS);
  //
  // which would imply in another bugs around node-fibers native re-build with
  // node-gyp, we just copy the whole `node_modules` folder that is officially
  // distributed with meteor, its 'ready to go and doesn't need to be rebuilt
  cp('-r', _METEOR_SERVER_LIB_MODS, _ELECTRIFIED_SERVER);
}

/*******************************************************************************
  PACKAGE APP
*******************************************************************************/

function package_app() {
  var name    = require(_ELECTRIFIED_PKG).name;
  var version = require(_ELECTRON_PKG_JSON).version;

  var command = [
    _ELECTRON_PACKAGER +' '+ _ELECTRIFIED + ' ' + name,
    '--out=' + _ELECTRIFIED_RELEASE,
    '--arch=' + _ARCH,
    '--platform=' + _PLATFORM,
    '--version='+ version
  ].join(' ');

  log(
    'packaging app for platform '+ _PLATFORM +
    ' '+ _ARCH + ' using electron v' + version
  );

  rm('-rf', _ELECTRIFIED_RELEASE);
  mkdir('-p', _ELECTRIFIED_RELEASE);

  exec(command, _SILENT_EXECS);

  log('wrote new app to ', _ELECTRIFIED_RELEASE);
}

/*******************************************************************************
  TEMPLATES
*******************************************************************************/

TEMPLATES = {
  index: [
    "var app       = require('app');",
    "var browser   = require('browser-window');",
    "var electrify = require('electrify');",
    "",
    "",
    "var window    = null;",
    "",
    "app.on('ready', function() {",
    "",
    "  window = new browser({",
    "    width: 1200,",
    "    height: 900,",
    "    'node-integration': false",
    "  });",
    "  ",
    "  electrify.boot(function() {",
    "    window.loadUrl(electrify.meteor_url);",
    "  });",
    "",
    "});",
    "",
    "",
    "app.on('will-quit', function(event) {",
    "  electrify.shutdown(app, event);",
    "});",
    "",
    "app.on('window-all-closed', function() {",
    "  app.quit();",
    "});"
  ].join('\r\n'),

  pkg: [
  '{',
  '  "name": "Electrified",',
  '  "main": "index.js",',
  '  "devDependencies": {',
  '    "electron-prebuilt": "^0.29.2",',
  '    "electron-packager": "^5.0.1",',
  '    "electrify": "'+ VERSION +'"',
  '  }',
  '}'
  ].join('\r\n'),

  gitignore: [
    '.DS_Store',
    '.dist',
    'app',
    'bin',
    'db',
    'node_modules'
  ].join('\r\n')
};

/*******************************************************************************
  SIMPLE FILE READ / WRITE / EXISTS helpers
*******************************************************************************/

function read(filepath, encoding) {
  if(exists(filepath))
    return fs.readFileSync(filepath, encoding || 'utf8');
}

function write(filepath, contents) {
  fs.writeFileSync(filepath, contents);
}

function exists(filepath) {
  return fs.existsSync(filepath);
}

/*******************************************************************************
  DEBUG
*******************************************************************************/

function log(){
  var args = Array.prototype.slice.call(arguments);
  console.log.apply(null, ['electrify: '].concat(args));
}<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var unzip = require('unzip');
var exec = require('child_process').exec;
var settings = require('./settings');
var sequentialForEach = require('./functions').sequentialForEach;

var OUT_DIR = settings.get('outputDir');
var REPORTS_DIR = settings.get('reportsDir');
var APKS_DIR = path.join(OUT_DIR, settings.get('apksDir'));
var ZIPS_DIR = path.join(OUT_DIR, settings.get('zipsDir'));
var TRAITS_DIR = path.join(OUT_DIR, settings.get('traitsDir'));
var APPS_INFO_DIR = path.join(OUT_DIR, settings.get('appsInfoDir'));

var apks = fs.readdirSync(APKS_DIR).filter(function(entry) {
    return entry.match('\\.apk$');
});

[ZIPS_DIR, TRAITS_DIR].forEach(function(d) {
    if(!fs.existsSync(d)) {
        fs.mkdirSync(d);
    }
});

var detectedTraits = {};
var apkIndex = 0;

nextStep();

// ---

function scanNextFile() {

    var apkFile = apks[apkIndex];
    var traitsFile = path.join(TRAITS_DIR, apkFile.replace(/-(\d+)\.apk$/, '.json'));

    console.log('TRAITS FILE', traitsFile);

    apkIndex++;

    if(!fs.existsSync(traitsFile)) {

        console.log('Scanning ' + Math.round(apkIndex/apks.length*100.0).toFixed(2) +'%');
        console.log('Results will go to ' + traitsFile);

        scanAPK(apkFile, function(resultTraits) {

            fs.writeFileSync(traitsFile, JSON.stringify(resultTraits, null, 4));

            nextStep();

        });

    } else {

        console.log('Already have traits for ' + apkFile + ', so skipping');
        nextStep();

    }

}

function nextStep() {
    if(apkIndex < apks.length) {

        process.nextTick(scanNextFile);

    } else {

        findTotals();

    }
}

function findTotals() {
    console.log('TIME TO TOTAL!');

    var totals = [];

    var traits = fs.readdirSync(TRAITS_DIR).filter(function(entry) {
        return entry.match('\\.json');
    });

    sequentialForEach(traits, function(traitFile, goOn) {

        var packageName = traitFile.replace('.json', '');
        var traitPath = path.join(TRAITS_DIR, traitFile);
        var infoPath = path.join(APPS_INFO_DIR, traitFile);
        var data = JSON.parse(fs.readFileSync(traitPath));

        console.log('INFO', infoPath);

        if(!fs.existsSync(infoPath)) {
            findAppInfo(packageName, function() {
                addEntry();
            });
        } else {
            addEntry();
        }

        function addEntry() {

            var appEntry = {
                name: packageName,
                traits: data
            };

            var info;

            try {
                info = JSON.parse(fs.readFileSync(infoPath));
                appEntry.info = info;
            } catch(e) { }

            var total = 0;
            console.log('###### ' + packageName + ' #######');
            data.forEach(function(tr) {
                console.log('+ ', tr.amount.toFixed(2), '\t', tr.reason);
                total += tr.amount;
            });

            appEntry.total = total.toFixed(2);

            console.log(total.toFixed(2) + ' TOTAL');
            console.log('\n---\n');

            totals.push(appEntry);

            process.nextTick(goOn);

        }

    }, function() {

        var jsonString = JSON.stringify(totals, null, 4);
        fs.writeFileSync(path.join(OUT_DIR, 'totals.json'), jsonString);
        fs.writeFileSync(path.join(REPORTS_DIR, 'totals.json'), jsonString);

    });

}

function findAppInfo(packageName, callback) {
    var url = 'https://play.google.com/store/apps/details?id=' + packageName;
    var outFile = packageName + '.json';

    console.log('find info for', packageName, url);
    exec("phantomjs phantom-query-pkg-info.js --url=" + url + " --output-file=" + outFile + " --output-dir=" + APPS_INFO_DIR, function(error, stdout, stderr) {
        console.log('done find app info', error);
        console.log(stdout);

        callback();
    });
}

function scanAPK(apkFile, doneCallback) {

    var cwd = process.cwd();
    var apkFullPath = path.join(APKS_DIR, apkFile);
    var zipPath = path.join(ZIPS_DIR, apkFile + '.zip');
    var extractDir = path.join(ZIPS_DIR, apkFile.replace('.apk', ''));
    var tmpDir = path.join(extractDir, 'TMP!!!');
    var classesDex = path.join(extractDir, 'classes.dex');
    var classesTmpDex = path.join(tmpDir, 'classes.dex');
    var classesJar = path.join(tmpDir, 'classes-dex2jar.jar');
    var ddxDir = path.join(extractDir, 'ddx');
    var ddxBin = path.join(cwd, 'ddx1.26.jar');
    var commands = [];

    if(!fs.existsSync(extractDir)) {
        commands.push('cp ' + apkFullPath + ' ' + zipPath);
        commands.push('unzip ' + zipPath + ' -d ' + extractDir);
    }

    if(!fs.existsSync(tmpDir)) {
        commands.push('mkdir ' + tmpDir);
    }

    commands.push('cp ' + classesDex + ' ' + tmpDir);

    if(!fs.existsSync(classesJar)) {

        commands.push('cd ' + tmpDir);

        // converts .dex into .jar
        commands.push('d2j-dex2jar.sh classes.dex');

        // extracts the files inside the .jar
        commands.push('jar xvf classes-dex2jar.jar');
        commands.push('cd ' + cwd);

    }

    // dex dex
    if(!fs.existsSync(ddxDir)) {
        commands.push('mkdir ' + ddxDir);
        commands.push('cp ' + classesDex + ' ' + ddxDir);
        commands.push('cd ' + ddxDir);
        commands.push('java -jar ' + ddxBin + ' -d ' + 'ddx' + ' classes.dex');
        commands.push('cd ' + cwd);
    }

    var cmd = commands.join('; ');
    console.log('Full command', cmd);

    exec(cmd, function(error, stdout, stderr) {
        // We can now actually analyse the APK contents
        detectHTML5ness(extractDir, doneCallback);
    });

}

function filterHTMLfiles(f) {
    return f.match(/\.html?$/i);
}

function filterJSfiles(f) {
    return f.match(/\.js$/i);
}

function filterClassFiles(f) {
    return f.match(/\.class$/i);
}

function filterPhonegapiness(f) {
    var expressions = [
        /com\/phonegap/,
        /com\/sencha/,
        /org\/apache\/cordova/,
        /org\/appcelerator/
    ];

    return expressions.some(function(expression) {
        return f.match(expression);
    });
}

function detectHTML5ness(apkDir, doneCallback) {
    console.log('Guessing html5-ness in', apkDir);

    var apkFiles = recursiveDirList(apkDir);
    var traits = [];

    // Possible HTML5 tell-tale signs

    // presence of html files
    var htmlFiles = apkFiles.filter(filterHTMLfiles);

    if(htmlFiles.length) {
        traits.push({
            amount: 5,
            reason: 'HTML files found in code',
            files: htmlFiles
        });
    }

    // presence of js files
    var jsFiles = apkFiles.filter(filterJSfiles);

    if(jsFiles.length) {
        traits.push({
            amount: 15,
            reason: 'JavaScript files found in code',
            files: jsFiles
        });
    }

    // classes.dex -> phonegap / ... / classes?
    var javaClasses = apkFiles.filter(filterClassFiles);
    var phonegapey = javaClasses.filter(filterPhonegapiness);

    if(phonegapey.length) {
        traits.push({
            amount: 50,
            reason: 'Phonegap or similar framework found',
            files: phonegapey
        });
    }

    // webview calls?
    // the uncompiled ddx sort-of-assembly files have some metadata
    // referring to calls to WebView-classes
    // So we'll increase the chances of this being a 'web' app each time
    // a file is using WebView or derived classes
    var ddxDir = path.join(apkDir, 'ddx');

    console.log(ddxDir);

    exec('grep -lr WebView ' + path.join(ddxDir, '*'), function(error, stdout, stderr) {

        var matches = stdout.split('\n');
        var num = matches.length;
        if(num) {
            traits.push({
                amount: num * 0.1,
                reason: num.toFixed(2) + ' calls to WebView',
                files: matches
            });
        }

        doneCallback(traits);

    });

}

function recursiveDirList(dir) {

    var results = [];
    var entries = fs.readdirSync(dir);

    entries.forEach(function(entry) {

        var fullPath = path.join(dir, entry);
        var stat = fs.statSync(fullPath);

        if(stat && stat.isDirectory()) {
            results = results.concat( recursiveDirList(fullPath) );
        } else {
            results.push(fullPath);
        }

    });

    return results;
}

<commit_msg>Use setImmediate for less recursively<commit_after>var fs = require('fs');
var path = require('path');
var unzip = require('unzip');
var exec = require('child_process').exec;
var settings = require('./settings');
var sequentialForEach = require('./functions').sequentialForEach;

var OUT_DIR = settings.get('outputDir');
var REPORTS_DIR = settings.get('reportsDir');
var APKS_DIR = path.join(OUT_DIR, settings.get('apksDir'));
var ZIPS_DIR = path.join(OUT_DIR, settings.get('zipsDir'));
var TRAITS_DIR = path.join(OUT_DIR, settings.get('traitsDir'));
var APPS_INFO_DIR = path.join(OUT_DIR, settings.get('appsInfoDir'));

var apks = fs.readdirSync(APKS_DIR).filter(function(entry) {
    return entry.match('\\.apk$');
});

[ZIPS_DIR, TRAITS_DIR].forEach(function(d) {
    if(!fs.existsSync(d)) {
        fs.mkdirSync(d);
    }
});

var detectedTraits = {};
var apkIndex = 0;

nextStep();

// ---

function scanNextFile() {

    var apkFile = apks[apkIndex];
    var traitsFile = path.join(TRAITS_DIR, apkFile.replace(/-(\d+)\.apk$/, '.json'));

    console.log('TRAITS FILE', traitsFile);

    apkIndex++;

    if(!fs.existsSync(traitsFile)) {

        console.log('Scanning ' + Math.round(apkIndex/apks.length*100.0).toFixed(2) +'%');
        console.log('Results will go to ' + traitsFile);

        scanAPK(apkFile, function(resultTraits) {

            fs.writeFileSync(traitsFile, JSON.stringify(resultTraits, null, 4));

            nextStep();

        });

    } else {

        console.log('Already have traits for ' + apkFile + ', so skipping');
        nextStep();

    }

}

function nextStep() {
    if(apkIndex < apks.length) {

        setImmediate(scanNextFile);

    } else {

        findTotals();

    }
}

function findTotals() {
    console.log('TIME TO TOTAL!');

    var totals = [];

    var traits = fs.readdirSync(TRAITS_DIR).filter(function(entry) {
        return entry.match('\\.json');
    });

    sequentialForEach(traits, function(traitFile, goOn) {

        var packageName = traitFile.replace('.json', '');
        var traitPath = path.join(TRAITS_DIR, traitFile);
        var infoPath = path.join(APPS_INFO_DIR, traitFile);
        var data = JSON.parse(fs.readFileSync(traitPath));

        console.log('INFO', infoPath);

        if(!fs.existsSync(infoPath)) {
            findAppInfo(packageName, function() {
                addEntry();
            });
        } else {
            addEntry();
        }

        function addEntry() {

            var appEntry = {
                name: packageName,
                traits: data
            };

            var info;

            try {
                info = JSON.parse(fs.readFileSync(infoPath));
                appEntry.info = info;
            } catch(e) { }

            var total = 0;
            console.log('###### ' + packageName + ' #######');
            data.forEach(function(tr) {
                console.log('+ ', tr.amount.toFixed(2), '\t', tr.reason);
                total += tr.amount;
            });

            appEntry.total = total.toFixed(2);

            console.log(total.toFixed(2) + ' TOTAL');
            console.log('\n---\n');

            totals.push(appEntry);

            process.nextTick(goOn);

        }

    }, function() {

        var jsonString = JSON.stringify(totals, null, 4);
        fs.writeFileSync(path.join(OUT_DIR, 'totals.json'), jsonString);
        fs.writeFileSync(path.join(REPORTS_DIR, 'totals.json'), jsonString);

    });

}

function findAppInfo(packageName, callback) {
    var url = 'https://play.google.com/store/apps/details?id=' + packageName;
    var outFile = packageName + '.json';

    console.log('find info for', packageName, url);
    exec("phantomjs phantom-query-pkg-info.js --url=" + url + " --output-file=" + outFile + " --output-dir=" + APPS_INFO_DIR, function(error, stdout, stderr) {
        console.log('done find app info', error);
        console.log(stdout);

        callback();
    });
}

function scanAPK(apkFile, doneCallback) {

    var cwd = process.cwd();
    var apkFullPath = path.join(APKS_DIR, apkFile);
    var zipPath = path.join(ZIPS_DIR, apkFile + '.zip');
    var extractDir = path.join(ZIPS_DIR, apkFile.replace('.apk', ''));
    var tmpDir = path.join(extractDir, 'TMP!!!');
    var classesDex = path.join(extractDir, 'classes.dex');
    var classesTmpDex = path.join(tmpDir, 'classes.dex');
    var classesJar = path.join(tmpDir, 'classes-dex2jar.jar');
    var ddxDir = path.join(extractDir, 'ddx');
    var ddxBin = path.join(cwd, 'ddx1.26.jar');
    var commands = [];

    if(!fs.existsSync(extractDir)) {
        commands.push('cp ' + apkFullPath + ' ' + zipPath);
        commands.push('unzip ' + zipPath + ' -d ' + extractDir);
    }

    if(!fs.existsSync(tmpDir)) {
        commands.push('mkdir ' + tmpDir);
    }

    commands.push('cp ' + classesDex + ' ' + tmpDir);

    if(!fs.existsSync(classesJar)) {

        commands.push('cd ' + tmpDir);

        // converts .dex into .jar
        commands.push('d2j-dex2jar.sh classes.dex');

        // extracts the files inside the .jar
        commands.push('jar xvf classes-dex2jar.jar');
        commands.push('cd ' + cwd);

    }

    // dex dex
    if(!fs.existsSync(ddxDir)) {
        commands.push('mkdir ' + ddxDir);
        commands.push('cp ' + classesDex + ' ' + ddxDir);
        commands.push('cd ' + ddxDir);
        commands.push('java -jar ' + ddxBin + ' -d ' + 'ddx' + ' classes.dex');
        commands.push('cd ' + cwd);
    }

    var cmd = commands.join('; ');
    console.log('Full command', cmd);

    exec(cmd, function(error, stdout, stderr) {
        // We can now actually analyse the APK contents
        detectHTML5ness(extractDir, doneCallback);
    });

}

function filterHTMLfiles(f) {
    return f.match(/\.html?$/i);
}

function filterJSfiles(f) {
    return f.match(/\.js$/i);
}

function filterClassFiles(f) {
    return f.match(/\.class$/i);
}

function filterPhonegapiness(f) {
    var expressions = [
        /com\/phonegap/,
        /com\/sencha/,
        /org\/apache\/cordova/,
        /org\/appcelerator/
    ];

    return expressions.some(function(expression) {
        return f.match(expression);
    });
}

function detectHTML5ness(apkDir, doneCallback) {
    console.log('Guessing html5-ness in', apkDir);

    var apkFiles = recursiveDirList(apkDir);
    var traits = [];

    // Possible HTML5 tell-tale signs

    // presence of html files
    var htmlFiles = apkFiles.filter(filterHTMLfiles);

    if(htmlFiles.length) {
        traits.push({
            amount: 5,
            reason: 'HTML files found in code',
            files: htmlFiles
        });
    }

    // presence of js files
    var jsFiles = apkFiles.filter(filterJSfiles);

    if(jsFiles.length) {
        traits.push({
            amount: 15,
            reason: 'JavaScript files found in code',
            files: jsFiles
        });
    }

    // classes.dex -> phonegap / ... / classes?
    var javaClasses = apkFiles.filter(filterClassFiles);
    var phonegapey = javaClasses.filter(filterPhonegapiness);

    if(phonegapey.length) {
        traits.push({
            amount: 50,
            reason: 'Phonegap or similar framework found',
            files: phonegapey
        });
    }

    // webview calls?
    // the uncompiled ddx sort-of-assembly files have some metadata
    // referring to calls to WebView-classes
    // So we'll increase the chances of this being a 'web' app each time
    // a file is using WebView or derived classes
    var ddxDir = path.join(apkDir, 'ddx');

    console.log(ddxDir);

    exec('grep -lr WebView ' + path.join(ddxDir, '*'), function(error, stdout, stderr) {

        var matches = stdout.split('\n');
        var num = matches.length;
        if(num) {
            traits.push({
                amount: num * 0.1,
                reason: num.toFixed(2) + ' calls to WebView',
                files: matches
            });
        }

        doneCallback(traits);

    });

}

function recursiveDirList(dir) {

    var results = [];
    var entries = fs.readdirSync(dir);

    entries.forEach(function(entry) {

        var fullPath = path.join(dir, entry);
        var stat = fs.statSync(fullPath);

        if(stat && stat.isDirectory()) {
            results = results.concat( recursiveDirList(fullPath) );
        } else {
            results.push(fullPath);
        }

    });

    return results;
}

<|endoftext|>


<commit_before>// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/

import gulp from 'gulp';
import debug from 'gulp-debug';
import changed from 'gulp-changed';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
import colors from 'colour';

import logger from '../logger';

import * as Paths from '../../src/shared/paths';
import * as Endpoints from '../../src/shared/endpoints';
import prodConfig from '../webpack/config.browser-frontend.prod';
import devConfig from '../webpack/config.browser-frontend.dev';

const WEBPACK_STATS_OPTIONS = {
  colors: true,
  warnings: false,
};

let gWebpackDevServer;

gulp.task('browser-frontend:copy-html', () => {
  return gulp.src(`${Paths.BROWSER_FRONTEND_SRC}/**/*.html`)
    .pipe(changed(Paths.BROWSER_FRONTEND_DST))
    .pipe(debug({ title: `Running ${colors.cyan('cp')}` }))
    .pipe(gulp.dest(Paths.BROWSER_FRONTEND_DST));
});

gulp.task('browser-frontend:copy-to-qbrt-shell', () => {
  return gulp.src(`${Paths.BROWSER_FRONTEND_DST}/**/*`)
    .pipe(changed(Paths.QBRT_RUNNER_SHELL_DST))
    .pipe(gulp.dest(Paths.QBRT_RUNNER_SHELL_DST));
});

gulp.task('browser-frontend:webpack', () => new Promise((resolve, reject) => {
  if (process.env.NODE_ENV !== 'development') {
    // In a production environment, webpack directly to disk.
    webpack(prodConfig, (err, stats) => {
      if (err) { reject(err); return; }
      if (stats) { logger.log(stats.toString(WEBPACK_STATS_OPTIONS)); }
      resolve();
    });
  } else {
    // In a development environment, webpack in memory and serve
    // the build with webpack-dev-server.
    const compiler = webpack(devConfig);
    const server = new WebpackDevServer(compiler, {
      ...devConfig.devServer,
      stats: WEBPACK_STATS_OPTIONS,
    });

    // We'll have to close the webpack dev server later, so store it as a global.
    // If we leave the server open, the build process will keep running forever.
    gWebpackDevServer = server;

    server.listen(
      Endpoints.WEBPACK_DEV_SERVER_PORT,
      Endpoints.WEBPACK_DEV_SERVER_HOST, resolve);
  }
}));

gulp.task('browser-frontend:build', gulp.series(
  'browser-frontend:copy-html',
  'browser-frontend:webpack',
  'browser-frontend:copy-to-qbrt-shell',
));

gulp.task('browser-frontend:build:cleanup', (cb) => {
  if (process.env.NODE_ENV === 'development') {
    gWebpackDevServer.close(cb);
  } else {
    cb();
  }
});
<commit_msg>Fix superficial linting regression from 9a1fb40 by running `npm run fix`<commit_after>// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/

import gulp from 'gulp';
import debug from 'gulp-debug';
import changed from 'gulp-changed';
import webpack from 'webpack';
import WebpackDevServer from 'webpack-dev-server';
import colors from 'colour';

import logger from '../logger';

import * as Paths from '../../src/shared/paths';
import * as Endpoints from '../../src/shared/endpoints';
import prodConfig from '../webpack/config.browser-frontend.prod';
import devConfig from '../webpack/config.browser-frontend.dev';

const WEBPACK_STATS_OPTIONS = {
  colors: true,
  warnings: false,
};

let gWebpackDevServer;

gulp.task('browser-frontend:copy-html', () =>
  gulp.src(`${Paths.BROWSER_FRONTEND_SRC}/**/*.html`)
    .pipe(changed(Paths.BROWSER_FRONTEND_DST))
    .pipe(debug({ title: `Running ${colors.cyan('cp')}` }))
    .pipe(gulp.dest(Paths.BROWSER_FRONTEND_DST)));

gulp.task('browser-frontend:copy-to-qbrt-shell', () =>
  gulp.src(`${Paths.BROWSER_FRONTEND_DST}/**/*`)
    .pipe(changed(Paths.QBRT_RUNNER_SHELL_DST))
    .pipe(gulp.dest(Paths.QBRT_RUNNER_SHELL_DST)));

gulp.task('browser-frontend:webpack', () => new Promise((resolve, reject) => {
  if (process.env.NODE_ENV !== 'development') {
    // In a production environment, webpack directly to disk.
    webpack(prodConfig, (err, stats) => {
      if (err) { reject(err); return; }
      if (stats) { logger.log(stats.toString(WEBPACK_STATS_OPTIONS)); }
      resolve();
    });
  } else {
    // In a development environment, webpack in memory and serve
    // the build with webpack-dev-server.
    const compiler = webpack(devConfig);
    const server = new WebpackDevServer(compiler, {
      ...devConfig.devServer,
      stats: WEBPACK_STATS_OPTIONS,
    });

    // We'll have to close the webpack dev server later, so store it as a global.
    // If we leave the server open, the build process will keep running forever.
    gWebpackDevServer = server;

    server.listen(
      Endpoints.WEBPACK_DEV_SERVER_PORT,
      Endpoints.WEBPACK_DEV_SERVER_HOST, resolve);
  }
}));

gulp.task('browser-frontend:build', gulp.series(
  'browser-frontend:copy-html',
  'browser-frontend:webpack',
  'browser-frontend:copy-to-qbrt-shell',
));

gulp.task('browser-frontend:build:cleanup', (cb) => {
  if (process.env.NODE_ENV === 'development') {
    gWebpackDevServer.close(cb);
  } else {
    cb();
  }
});
<|endoftext|>