<commit_before>export { NeuralWorker_Proxies as Proxies };

import * as Pool from "../../util/Pool.js";
import * as Recyclable from "../../util/Recyclable.js";
import * as GSheets from "../util/GSheets.js";
//import * as ValueMax from "../util/ValueMax.js";
import { Proxy as WorkerProxy } from "./NeuralWorker_Proxy.js";
//import * as NeuralNetProgress from "./NetProgress.js";

/**
 * The container of WorkerProxy. It orchestrates these WorkerProxy. Especially, it
 * transfers (scaled) source image data to and from web worker. This could maximize
 * parallel computing under the restriction transferring source image data to every
 * web worker serially.
 *
 * Every worker handles one neural network. When processTensor() is called, the input
 * (usually a large memory block) will be transffered to the 1st worker to start
 * computing, and then transffered to the 2nd worker to start computing, ... etc.
 *
 * When passing large data by Worker.postMessage(), it is preferred by transferring
 * (not by copying). If the large data wants to be transferred (not copied) to many
 * workers, the only possible way is to transferring them serially.
 *
 * However, serially transferring hurts the performance. Workers are better to compute
 * parallelly. So every worker should transfer the (possible scaled) source image data
 * back to this WorkerProxies, and keep computing neural network at the same. And
 * then, this WorkerProxies will transfer the source image data to the next worker
 * as soon as possible.
 *
 * Finally, this WorkerProxies collects all web workers' processTensor() results in
 * a promise. The promise will resolve with an array of typed-array. Every typed-array
 * is the output of one neural network.
 *
 *
 *
 * @param {string[]} evolutionVersusRangeArray
 *   A string array. Every element is the spreadsheet range description string for an
 * evolution versus (i.e. parent versus offspring).
 */
class NeuralWorker_Proxies extends Recyclable.Root {

  /**
   * Used as default NeuralWorker.Proxies provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "NeuralWorker.Proxies.Pool",
    NeuralWorker_Proxies, NeuralWorker_Proxies.setAsConstructor );

  /** */
  constructor() {
    super();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
  }

  /** @override */
  static setAsConstructor() {
    super.setAsConstructor();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
    return this;
  }

  /** @override */
  static setAsConstructor_self() {
  }

  /** @override */
  disposeResources() {

    this.evolutionVersusRangeArray = null; // Normal array. Just nullify it.

//!!! ...unfinished... (2022/08/26)
    this.disposeWorkers();

    super.disposeResources();
  }

  /**
   * Initialize this worker proxy controller. It will create two web workers and inform
   * them to create a neural network per worker.
   *
   * @param {string} tensorflowJsURL
   *   The URL of tensorflow javascript library. Every worker will load the library
   * from the URL.
   *
   * @param {Net.Config} neuralNetConfig
   *   The configuration of the neural network which will be created by this web worker.
   *
   * @param {string} weightsSpreadsheetId
   *   The Google Sheets spreadsheetId of neural network weights. Every worker will
   * load weights from the spreadsheet to initialize one neural network.
   *
   * @param {string} weightsAPIKey
   *   The API key for accessing the Google Sheets spreadsheet of neural network weights.
   *   - If null, Google Visualization Table Query API will be used.
   *   - If not null, Google Sheets API v4 will be used.
   */
  init( tensorflowJsURL, neuralNetConfig, weightsSpreadsheetId, weightsAPIKey ) {

    this.tensorflowJsURL = tensorflowJsURL;
    this.neuralNetConfig = neuralNetConfig;
    this.weightsSpreadsheetId = weightsSpreadsheetId;
    this.weightsAPIKey = weightsAPIKey;

    // The current processing id. Negative means processTensor() has not been called.
    // Every processTensor() call will use a new id.
    //
    // Q: What if processingId become too large (e.g. infinity)?
    // A: Because Number.MAX_SAFE_INTEGER is pretty large (at least, 2 ** 52 ),
    //    it is not so easy to become out of bounds.
    //
    this.processingId = -1;

    this.hardwareConcurrency = navigator.hardwareConcurrency; // logical CPU count.

    // Two web workers are sufficient.
    //
    // Although we might want create as many web worker as logical CPU count, it might
    // not need because our neural networks are learning by differential evolution.
    // Differential evolution evaluates just two entities every time.
    //

//!!! ...unfinished... (2022/08/26) really?

    // Note: How could the two neural networks determine all the actions of so many
    //       game objects? The method is to let the output of one neural network
    //       contains all actions of all game objects. But only half of the output
    //       actions will be used because one neural network only control one alignment
    //       of the game world.
    //
    let totalWorkerCount = this.totalWorkerCount = 2;

    // Statistics of progress of all workers' initialization.
    this.initProgressAll = new NeuralNetProgress.InitProgressAll();

    this.workerProxyArray = Pool.OwnerArray.Pool.get_or_create_by();
    this.workerProxyArray.length = totalWorkerCount;

    for ( let i = 0; i < totalWorkerCount; ++i ) {
      let initProgress = this.initProgressAll.childrren[ i ];
      let workerProxy = this.workerProxyArray[ i ] = WorkerProxy.Pool.get_or_create_by();
      workerProxy.init(
        i, tensorflowJsURL, neuralNetConfig, weightsSpreadsheetId, weightsAPIKey,
        initProgress
      );
    }

    // Pre-allocation for reducing re-allocation.
    this.processTensorPromiseArray = new Array( totalWorkerCount );
  }

  /**
   *
   */
  disposeWorkers() {
    if ( this.workerProxyArray ) {
      this.workerProxyArray.disposeResources_and_recycleToPool();
      this.workerProxyArray = null;
    }

    this.initProgressAll = null;
  }

  /** Load all evolution versus weights ranges. */
  async evolutionVersusRangeArray_loadAsync() {
    // The summary is at the first column of the first (i.e. left most) sheet.
    const range = "A:A";

    let urlComposer = GSheets.UrlComposer.Pool.get_or_create_by(
      this.weightsSpreadsheetId, range, this.weightsAPIKey );

    let evolutionVersusRangeArrayArray
      = urlComposer.fetchAsync_JSON_ColumnMajorArrayArray();

    // Only the first column (i.e. column[ 0 ]) has range description string.
    this.evolutionVersusRangeArray = evolutionVersusRangeArrayArray[ 0 ];

    urlComposer.disposeResources_and_recycleToPool();
    urlComposer = null;
  }

  /**
   * Download image data from source canvas. Pass source image data to every web
   * worker serially by transferring.
   *
   * @param {HTMLCanvasElement} sourceCanvas
   *   The source canvas to be processed. Its shape [ height, width, channelCount ]
   * should be the same as the size which is used when training the neural network.
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker processing promises of
   * the same processingId are resolved. The promise resolves with an array of
   * typed-array. Every typed-array comes from the output tensor of one worker's
   * neural network.
   */
  async processCanvas_Async_ByTransfer( sourceCanvas ) {
    let ctx = sourceCanvas.getContext( '2d' );
    let sourceImageData = ctx.getImageData(
      0, 0, sourceCanvas.width, sourceCanvas.height );
    return this.processImageData_Async_ByTransfer( sourceImageData );
  }

  /**
   * Pass source image data to every web worker serially by transferring.
   *
   * @param {ImageData} sourceImageData
   *   The source image data to be processed. Its shape [ height, width, channelCount ]
   * should be the same as the size which is used when training the neural network.
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker processing promises of
   * the same processingId are resolved. The promise resolves with an array of
   * typed-array. Every typed-array comes from the output tensor of one worker's
   * neural network.
   */
  async processImageData_Async_ByTransfer( sourceImageData ) {

    // Generate a new processing id so that the result returned from worker could
    // be distinguished.
    ++this.processingId;
    let processingId = this.processingId;

    let workerProxy, processRelayPromises, sourceTypedArray;

    // For first (i == 0) web worker, passing source ImageData.
    workerProxy = this.workerProxyArray[ 0 ];
    this.processTensorPromiseArray[ 0 ]
      = workerProxy.imageData_transferBack_processTensor_async(
          processingId, sourceImageData );
    // Now, sourceImageData.data.buffer has become invalid because it is transferred
    // (not copied) to the above web worker.

    // For all other (i >= 1) web workers, passing scaled source typed-array.
    for ( let i = 1; i < this.workerProxyArray.length; ++i ) {

      // Wait the previous web worker transferring back the scaled source typed-array.
      // (after it has been copied into a new tensor inside the web worker).
      processRelayPromises = workerProxy.processRelayPromisesMap.get( processingId );
      sourceTypedArray = await processRelayPromises.relay.promise;

      workerProxy = this.workerProxyArray[ i ];
      this.processTensorPromiseArray[ i ]
        = workerProxy.typedArray_transferBack_processTensor_async(
            processingId, sourceTypedArray );
      // Now, sourceTypedArray.buffer has become invalid because it is transferred
      // (not copied) to the above web worker.
    }

    // Note: Array push() is faster than unshift(), and unshift() is faster than concat().

    // Since all web worker has received the source image data (although serially),
    // wait for all them done.
    let promiseAllSettled = Promise.allSettled( this.processTensorPromiseArray );
    return promiseAllSettled;
  }

  /**
   * Download image data from source canvas, convert to tensor, scale, pass source
   * image data to every web worker parallelly by copying.
   *
   * The tensorflow.js library should have been loaded so that the source image
   * scaling could be done here (not in web worker).
   *
   * @param {HTMLCanvasElement} sourceCanvas
   *   The source canvas to be processed. Its shape [ height, width, channelCount ]
   * should be the same as the size which is used when training the neural network.
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker processing promises of
   * the same processingId are resolved. The promise resolves with an array of
   * typed-array. Every typed-array comes from the output tensor of one worker's
   * neural network.
   */
  async processCanvas_Async_ByCopy( sourceCanvas ) {

    // Create (scaled) source tensor so that every web worker needs not scale again
    // and easier to re-create source tensor.
    //
    // The drawback is that the tensorflow.js library should have been loaded here.
    // And the image scaling is done here (not in web worker).
    //
    let scaledSourceTensor
      = this.neuralNetConfig.create_ScaledSourceTensor_from_PixelData( sourceCanvas );

    let sourceTypedArray = await scaledSourceTensor.data();

    // Discard the source tensor because type-array (not tensor) will be passed to
    // web worker
    scaledSourceTensor.dispose();

    // Generate a new processing id so that the result returned from worker could be
    // distinguished.
    ++this.processingId;
    let processingId = this.processingId;

    let workerProxy, processRelayPromises;

    for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
      workerProxy = this.workerProxyArray[ i ];
      this.processTensorPromiseArray[ i ]
        = workerProxy.typedArray_processTensor_async( processingId, sourceTypedArray );
      // The sourceTypedArray is still valid here because it is copied (not
      // transferred) to the above web worker.
    }

    // Since all web worker has received the source typed-array (parallelly), wait
    // for all them done.
    let promiseAllSettled = Promise.allSettled( this.processTensorPromiseArray );
    return promiseAllSettled;
  }

}
<commit_msg>should shuffle the list.<commit_after>export { NeuralWorker_Proxies as Proxies };

import * as Pool from "../../util/Pool.js";
import * as Recyclable from "../../util/Recyclable.js";
import * as GSheets from "../util/GSheets.js";
//import * as ValueMax from "../util/ValueMax.js";
import { Proxy as WorkerProxy } from "./NeuralWorker_Proxy.js";
//import * as NeuralNetProgress from "./NetProgress.js";

/**
 * The container of WorkerProxy. It orchestrates these WorkerProxy. Especially, it
 * transfers (scaled) source image data to and from web worker. This could maximize
 * parallel computing under the restriction transferring source image data to every
 * web worker serially.
 *
 * Every worker handles one neural network. When processTensor() is called, the input
 * (usually a large memory block) will be transffered to the 1st worker to start
 * computing, and then transffered to the 2nd worker to start computing, ... etc.
 *
 * When passing large data by Worker.postMessage(), it is preferred by transferring
 * (not by copying). If the large data wants to be transferred (not copied) to many
 * workers, the only possible way is to transferring them serially.
 *
 * However, serially transferring hurts the performance. Workers are better to compute
 * parallelly. So every worker should transfer the (possible scaled) source image data
 * back to this WorkerProxies, and keep computing neural network at the same. And
 * then, this WorkerProxies will transfer the source image data to the next worker
 * as soon as possible.
 *
 * Finally, this WorkerProxies collects all web workers' processTensor() results in
 * a promise. The promise will resolve with an array of typed-array. Every typed-array
 * is the output of one neural network.
 *
 *
 *
 * @param {string[]} evolutionVersusRangeArray
 *   A string array. Every element is the spreadsheet range description string for an
 * evolution versus (i.e. parent versus offspring).
 */
class NeuralWorker_Proxies extends Recyclable.Root {

  /**
   * Used as default NeuralWorker.Proxies provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "NeuralWorker.Proxies.Pool",
    NeuralWorker_Proxies, NeuralWorker_Proxies.setAsConstructor );

  /** */
  constructor() {
    super();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
  }

  /** @override */
  static setAsConstructor() {
    super.setAsConstructor();
    NeuralWorker_Proxies.setAsConstructor_self.call( this );
    return this;
  }

  /** @override */
  static setAsConstructor_self() {
  }

  /** @override */
  disposeResources() {

    this.evolutionVersusRangeArray = null; // Normal array. Just nullify it.

//!!! ...unfinished... (2022/08/26)
    this.disposeWorkers();

    super.disposeResources();
  }

  /**
   * Initialize this worker proxy controller. It will create two web workers and inform
   * them to create a neural network per worker.
   *
   * @param {string} tensorflowJsURL
   *   The URL of tensorflow javascript library. Every worker will load the library
   * from the URL.
   *
   * @param {Net.Config} neuralNetConfig
   *   The configuration of the neural network which will be created by this web worker.
   *
   * @param {string} weightsSpreadsheetId
   *   The Google Sheets spreadsheetId of neural network weights. Every worker will
   * load weights from the spreadsheet to initialize one neural network.
   *
   * @param {string} weightsAPIKey
   *   The API key for accessing the Google Sheets spreadsheet of neural network weights.
   *   - If null, Google Visualization Table Query API will be used.
   *   - If not null, Google Sheets API v4 will be used.
   */
  init( tensorflowJsURL, neuralNetConfig, weightsSpreadsheetId, weightsAPIKey ) {

    this.tensorflowJsURL = tensorflowJsURL;
    this.neuralNetConfig = neuralNetConfig;
    this.weightsSpreadsheetId = weightsSpreadsheetId;
    this.weightsAPIKey = weightsAPIKey;

    // The current processing id. Negative means processTensor() has not been called.
    // Every processTensor() call will use a new id.
    //
    // Q: What if processingId become too large (e.g. infinity)?
    // A: Because Number.MAX_SAFE_INTEGER is pretty large (at least, 2 ** 52 ),
    //    it is not so easy to become out of bounds.
    //
    this.processingId = -1;

    this.hardwareConcurrency = navigator.hardwareConcurrency; // logical CPU count.

    // Two web workers are sufficient.
    //
    // Although we might want create as many web worker as logical CPU count, it might
    // not need because our neural networks are learning by differential evolution.
    // Differential evolution evaluates just two entities every time.
    //

//!!! ...unfinished... (2022/08/26) really?

    // Note: How could the two neural networks determine all the actions of so many
    //       game objects? The method is to let the output of one neural network
    //       contains all actions of all game objects. But only half of the output
    //       actions will be used because one neural network only control one alignment
    //       of the game world.
    //
    let totalWorkerCount = this.totalWorkerCount = 2;

    // Statistics of progress of all workers' initialization.
    this.initProgressAll = new NeuralNetProgress.InitProgressAll();

    this.workerProxyArray = Pool.OwnerArray.Pool.get_or_create_by();
    this.workerProxyArray.length = totalWorkerCount;

    for ( let i = 0; i < totalWorkerCount; ++i ) {
      let initProgress = this.initProgressAll.childrren[ i ];
      let workerProxy = this.workerProxyArray[ i ] = WorkerProxy.Pool.get_or_create_by();
      workerProxy.init(
        i, tensorflowJsURL, neuralNetConfig, weightsSpreadsheetId, weightsAPIKey,
        initProgress
      );
    }

    // Pre-allocation for reducing re-allocation.
    this.processTensorPromiseArray = new Array( totalWorkerCount );
  }

  /**
   *
   */
  disposeWorkers() {
    if ( this.workerProxyArray ) {
      this.workerProxyArray.disposeResources_and_recycleToPool();
      this.workerProxyArray = null;
    }

    this.initProgressAll = null;
  }

  /** Load all evolution versus weights ranges. */
  async evolutionVersusRangeArray_loadAsync() {
    // The summary is at the first column of the first (i.e. left most) sheet.
    const range = "A:A";

    let urlComposer = GSheets.UrlComposer.Pool.get_or_create_by(
      this.weightsSpreadsheetId, range, this.weightsAPIKey );

    let evolutionVersusRangeArrayArray
      = urlComposer.fetchAsync_JSON_ColumnMajorArrayArray();

    // Only the first column (i.e. column[ 0 ]) has range description string.
    this.evolutionVersusRangeArray = evolutionVersusRangeArrayArray[ 0 ];

    urlComposer.disposeResources_and_recycleToPool();
    urlComposer = null;

//!!! ...unfinished... (2022/08/26) should shuffle the list.

  }

  /**
   * Download image data from source canvas. Pass source image data to every web
   * worker serially by transferring.
   *
   * @param {HTMLCanvasElement} sourceCanvas
   *   The source canvas to be processed. Its shape [ height, width, channelCount ]
   * should be the same as the size which is used when training the neural network.
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker processing promises of
   * the same processingId are resolved. The promise resolves with an array of
   * typed-array. Every typed-array comes from the output tensor of one worker's
   * neural network.
   */
  async processCanvas_Async_ByTransfer( sourceCanvas ) {
    let ctx = sourceCanvas.getContext( '2d' );
    let sourceImageData = ctx.getImageData(
      0, 0, sourceCanvas.width, sourceCanvas.height );
    return this.processImageData_Async_ByTransfer( sourceImageData );
  }

  /**
   * Pass source image data to every web worker serially by transferring.
   *
   * @param {ImageData} sourceImageData
   *   The source image data to be processed. Its shape [ height, width, channelCount ]
   * should be the same as the size which is used when training the neural network.
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker processing promises of
   * the same processingId are resolved. The promise resolves with an array of
   * typed-array. Every typed-array comes from the output tensor of one worker's
   * neural network.
   */
  async processImageData_Async_ByTransfer( sourceImageData ) {

    // Generate a new processing id so that the result returned from worker could
    // be distinguished.
    ++this.processingId;
    let processingId = this.processingId;

    let workerProxy, processRelayPromises, sourceTypedArray;

    // For first (i == 0) web worker, passing source ImageData.
    workerProxy = this.workerProxyArray[ 0 ];
    this.processTensorPromiseArray[ 0 ]
      = workerProxy.imageData_transferBack_processTensor_async(
          processingId, sourceImageData );
    // Now, sourceImageData.data.buffer has become invalid because it is transferred
    // (not copied) to the above web worker.

    // For all other (i >= 1) web workers, passing scaled source typed-array.
    for ( let i = 1; i < this.workerProxyArray.length; ++i ) {

      // Wait the previous web worker transferring back the scaled source typed-array.
      // (after it has been copied into a new tensor inside the web worker).
      processRelayPromises = workerProxy.processRelayPromisesMap.get( processingId );
      sourceTypedArray = await processRelayPromises.relay.promise;

      workerProxy = this.workerProxyArray[ i ];
      this.processTensorPromiseArray[ i ]
        = workerProxy.typedArray_transferBack_processTensor_async(
            processingId, sourceTypedArray );
      // Now, sourceTypedArray.buffer has become invalid because it is transferred
      // (not copied) to the above web worker.
    }

    // Note: Array push() is faster than unshift(), and unshift() is faster than concat().

    // Since all web worker has received the source image data (although serially),
    // wait for all them done.
    let promiseAllSettled = Promise.allSettled( this.processTensorPromiseArray );
    return promiseAllSettled;
  }

  /**
   * Download image data from source canvas, convert to tensor, scale, pass source
   * image data to every web worker parallelly by copying.
   *
   * The tensorflow.js library should have been loaded so that the source image
   * scaling could be done here (not in web worker).
   *
   * @param {HTMLCanvasElement} sourceCanvas
   *   The source canvas to be processed. Its shape [ height, width, channelCount ]
   * should be the same as the size which is used when training the neural network.
   *
   * @return {Promise}
   *   Return a promise which will be resolved when all worker processing promises of
   * the same processingId are resolved. The promise resolves with an array of
   * typed-array. Every typed-array comes from the output tensor of one worker's
   * neural network.
   */
  async processCanvas_Async_ByCopy( sourceCanvas ) {

    // Create (scaled) source tensor so that every web worker needs not scale again
    // and easier to re-create source tensor.
    //
    // The drawback is that the tensorflow.js library should have been loaded here.
    // And the image scaling is done here (not in web worker).
    //
    let scaledSourceTensor
      = this.neuralNetConfig.create_ScaledSourceTensor_from_PixelData( sourceCanvas );

    let sourceTypedArray = await scaledSourceTensor.data();

    // Discard the source tensor because type-array (not tensor) will be passed to
    // web worker
    scaledSourceTensor.dispose();

    // Generate a new processing id so that the result returned from worker could be
    // distinguished.
    ++this.processingId;
    let processingId = this.processingId;

    let workerProxy, processRelayPromises;

    for ( let i = 0; i < this.workerProxyArray.length; ++i ) {
      workerProxy = this.workerProxyArray[ i ];
      this.processTensorPromiseArray[ i ]
        = workerProxy.typedArray_processTensor_async( processingId, sourceTypedArray );
      // The sourceTypedArray is still valid here because it is copied (not
      // transferred) to the above web worker.
    }

    // Since all web worker has received the source typed-array (parallelly), wait
    // for all them done.
    let promiseAllSettled = Promise.allSettled( this.processTensorPromiseArray );
    return promiseAllSettled;
  }

}
<|endoftext|>


<commit_before>// Backbone Models and Collections to be used both client/server side.
// Note that these models are read-only - no `PUT/POST/DELETE` expected.

// Requires for the server-side context. *TODO* Note that `var` is omitted here
// because even within the `if()` IE will wipe globally defined variables if
// `var` is included, leaving us with broken objects.
if (typeof require !== 'undefined') {
    _ = require('underscore')._,
    Backbone = require('backbone.js'),
    Bones = require('bones');
}

var Bones = Bones || {};
Bones.models = Bones.models || {};

// Tileset
// ---
// A single tileset, corresponding to an `.mbtiles` file. `model.id` is the
// file basename, e.g. `foo.mbtiles` has an `id` of `foo`.
Bones.models.Tileset = Backbone.Model.extend({
    initialize: function(attributes) {
        Backbone.Model.prototype.initialize.call(this, attributes);
        // Convert representation of baselayer into a true Tileset model.
        if (typeof this.get('baselayer') !== 'undefined') {
            this.set({ baselayer: new Tileset(this.get('baselayer')) });
        }
    },
    parse: function(response){
        var model = Backbone.Model.prototype.parse.call(this, response);
        // Convert representation of baselayer into a true Tileset model.
        if (typeof model.baselayer !== 'undefined') {
            model.baselayer = new Tileset(model.baselayer);
        }
        return model;
    },
    url: function() {
        return '/api/Tileset/' + this.id;
    },
    // Return the base URLs of TileStream tile servers including a single
    // trailing slash, e.g. http://localhost:8889/ in an Array.
    layerURL: function() {
        // Servers defined in `Bones.settings`.
        if (Bones.settings.tileHost.length) {
            return Bones.settings.tileHost;
        // Autodetect server from window object.
        } else if (window.location && window.location.hostname) {
            // Attempt to autodetect URL.
            var baseURL = window.location.protocol + '//' + window.location.hostname + ':' + Bones.settings.tilePort;
            var args = window.location.pathname.split('/');
            // Path already ends with trailing slash.
            if (args[args.length - 1] === '') {
                return [baseURL + args.join('/')];
            // index.html or similar trailing filename.
            } else if (args[args.length - 1].indexOf('.') !== -1) {
                args.pop();
                return [baseURL + args.join('/') + '/'];
            // Path beyond domain.
            } else {
                return [baseURL + args.join('/') + '/'];
            }
        // Server side, *TODO* needs a solution.
        } else {
            return ['http://localhost:8888/'];
        }
    },
    // Get ZXY of tile of tileset's center and minzoom. From [OSM wiki][1].
    // [1]: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#lon.2Flat_to_tile_numbers_2
    toZXY: function() {
        var center = this.get('center');
        var z = this.get('minzoom');
        var lat_rad = center.lat * Math.PI / 180 * -1; // -1 for TMS (flipped from OSM)
        var x = parseInt((center.lon + 180.0) / 360.0 * Math.pow(2, z));
        var y = parseInt((1.0 - Math.log(Math.tan(lat_rad) + (1 / Math.cos(lat_rad))) / Math.PI) / 2.0 * Math.pow(2, z));
        return [z, x, y];
    },
    thumb: function(zxy) {
        zxy = zxy || this.toZXY();
        return this.layerURL()[0] + ['1.0.0', this.get('id'), zxy[0], zxy[1], zxy[2]].join('/') + '.png';
    },
    wax: function() {
        return {
            layers: [this.get('id')],
            center: [
                this.get('center').lon,
                this.get('center').lat,
                this.get('minzoom')
            ]
        };
    }
});

// Tilesets
// --------
// Collection of all tileset models.
Bones.models.Tilesets = Backbone.Collection.extend({
    model: Bones.models.Tileset,
    url: '/api/Tileset',
    comparator: function(model) {
        return (model.get('name') || model.get('id')).toLowerCase();
    }
});

(typeof module !== 'undefined') && (module.exports = Bones.models);

<commit_msg>Use a mid-level zoom level for thumbnails.<commit_after>// Backbone Models and Collections to be used both client/server side.
// Note that these models are read-only - no `PUT/POST/DELETE` expected.

// Requires for the server-side context. *TODO* Note that `var` is omitted here
// because even within the `if()` IE will wipe globally defined variables if
// `var` is included, leaving us with broken objects.
if (typeof require !== 'undefined') {
    _ = require('underscore')._,
    Backbone = require('backbone.js'),
    Bones = require('bones');
}

var Bones = Bones || {};
Bones.models = Bones.models || {};

// Tileset
// ---
// A single tileset, corresponding to an `.mbtiles` file. `model.id` is the
// file basename, e.g. `foo.mbtiles` has an `id` of `foo`.
Bones.models.Tileset = Backbone.Model.extend({
    initialize: function(attributes) {
        Backbone.Model.prototype.initialize.call(this, attributes);
        // Convert representation of baselayer into a true Tileset model.
        if (typeof this.get('baselayer') !== 'undefined') {
            this.set({ baselayer: new Tileset(this.get('baselayer')) });
        }
    },
    parse: function(response){
        var model = Backbone.Model.prototype.parse.call(this, response);
        // Convert representation of baselayer into a true Tileset model.
        if (typeof model.baselayer !== 'undefined') {
            model.baselayer = new Tileset(model.baselayer);
        }
        return model;
    },
    url: function() {
        return '/api/Tileset/' + this.id;
    },
    // Return the base URLs of TileStream tile servers including a single
    // trailing slash, e.g. http://localhost:8889/ in an Array.
    layerURL: function() {
        // Servers defined in `Bones.settings`.
        if (Bones.settings.tileHost.length) {
            return Bones.settings.tileHost;
        // Autodetect server from window object.
        } else if (window.location && window.location.hostname) {
            // Attempt to autodetect URL.
            var baseURL = window.location.protocol + '//' + window.location.hostname + ':' + Bones.settings.tilePort;
            var args = window.location.pathname.split('/');
            // Path already ends with trailing slash.
            if (args[args.length - 1] === '') {
                return [baseURL + args.join('/')];
            // index.html or similar trailing filename.
            } else if (args[args.length - 1].indexOf('.') !== -1) {
                args.pop();
                return [baseURL + args.join('/') + '/'];
            // Path beyond domain.
            } else {
                return [baseURL + args.join('/') + '/'];
            }
        // Server side, *TODO* needs a solution.
        } else {
            return ['http://localhost:8888/'];
        }
    },
    // Get the "mid" zoom level of this tileset. Useful for thumbnails/overview
    // images for tilesets.
    thumbzoom: function() {
        var range = this.get('maxzoom') - this.get('minzoom');
        if (range <= 1) {
            return this.get('maxzoom');
        } else {
            return Math.floor(range * 0.5) + this.get('minzoom');
        }
    },
    // Get ZXY of tile of tileset's center and minzoom. From [OSM wiki][1].
    // [1]: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#lon.2Flat_to_tile_numbers_2
    toZXY: function() {
        var center = this.get('center');
        var z = this.thumbzoom();
        var lat_rad = center.lat * Math.PI / 180 * -1; // -1 for TMS (flipped from OSM)
        var x = parseInt((center.lon + 180.0) / 360.0 * Math.pow(2, z));
        var y = parseInt((1.0 - Math.log(Math.tan(lat_rad) + (1 / Math.cos(lat_rad))) / Math.PI) / 2.0 * Math.pow(2, z));
        return [z, x, y];
    },
    thumb: function(zxy) {
        zxy = zxy || this.toZXY();
        return this.layerURL()[0] + ['1.0.0', this.get('id'), zxy[0], zxy[1], zxy[2]].join('/') + '.png';
    },
    wax: function() {
        return {
            layers: [this.get('id')],
            center: [
                this.get('center').lon,
                this.get('center').lat,
                this.thumbzoom()
            ]
        };
    }
});

// Tilesets
// --------
// Collection of all tileset models.
Bones.models.Tilesets = Backbone.Collection.extend({
    model: Bones.models.Tileset,
    url: '/api/Tileset',
    comparator: function(model) {
        return (model.get('name') || model.get('id')).toLowerCase();
    }
});

(typeof module !== 'undefined') && (module.exports = Bones.models);

<|endoftext|>


<commit_before>// Raphael draggable plugin
// Matt Ephraim - 2009
(function() {
  /* Raphael paper extensions */
  Raphael.fn.draggable = {};
  Raphael.fn.draggable.enable = function() {
    var paper = this;

    var currentDraggable;
    paper.draggable.enabled = true;

    paper.draggable.current = function(element) {
      if (element) {
  	    currentDraggable = element;
  	    return paper;
  	  }
  	  else {
  	    return currentDraggable;
  	  }
  	};

  	paper.draggable.clearCurrent = function() {
  	  currentDraggable = null;
  	  return paper;
  	}

  	overrideElements(paper);
  	overrideSet(paper);
    return paper;
  };

  /* Raphael element extensions */
  function overrideElements(paper) {
    var elementTypes = ['circle', 'rect', 'ellipse', 'image', 'text', 'path'];
  	for(var i = 0; i < elementTypes.length; i++) {
  		overrideElementFunc(paper, elementTypes[i]);
  	}
  }

  function overrideElementFunc(paper, elementType) {
		var oldFunc = paper[elementType];
		paper[elementType] = function() {
			var element = oldFunc.apply(paper, arguments);
			element.draggable = new DraggableExtension(element);
			return element;
		};
	}

	/* Raphael set extensions */
	function overrideSet(paper) {
	  var oldFunc = paper.set;
	  paper.set = function() {
	    var set = oldFunc.apply(paper, arguments);
	    set.paper = paper;
	    set.draggable = new DraggableExtension(set);

	    overrideSetFunctions(set);
	    return set;
	  };
	}

	function overrideSetFunctions(set) {
	  var oldPushFunc = set.push;
	  set.push = function(element) {
	    oldPushFunc.apply(set, arguments);
	    if (element.draggable) {
	      element.draggable.parent = set;

	      if (set.draggable.enabled) {
	        element.draggable.enable();
	      }
	    }
	  }
	}

	/* Extension for objects that adds draggable functionality */
	var DraggableExtension = function (element) {
	  var paper = element.paper,
	      lastX,
	      lastY;

	  this.element = element;
	  this.enabled = false;

    element.mousedown(function(event) {
      if (paper.draggable.current() || !element.draggable.enabled)
        return;

      element.draggable.isDragging = true;

      paper.draggable.current(element);
      lastX = event.clientX;
  		lastY = event.clientY;
    });

    element.mousemove(function(event) {
      if (element.draggable.isDragging) {
        console.log(element.draggable.parent);
        var toMove = element.draggable.parent || element;

        var transX = event.clientX - lastX;
        var transY = event.clientY - lastY;

        toMove.translate(transX, transY);
        lastX = event.clientX;
        lastY = event.clientY;
      }
    });

	  element.mouseup(stopDragging);
	  element.mouseout(stopDragging);

	  function stopDragging() {
	    element.draggable.isDragging = false;
	    paper.draggable.clearCurrent();
	  }
	};

  DraggableExtension.prototype = {
    enable: function() {
      this.enabled = true;
      return this.element;
    },

    disable: function() {
      this.enabled = false;
      return this.element;
    }
  }
})();<commit_msg>Updated copyright info (whatever that means)<commit_after>// raphael.draggable plugin
// Copyright (c) 2009 Matt Ephraim
(function() {
  /* Raphael paper extensions */
  Raphael.fn.draggable = {};
  Raphael.fn.draggable.enable = function() {
    var paper = this;

    var currentDraggable;
    paper.draggable.enabled = true;

    paper.draggable.current = function(element) {
      if (element) {
  	    currentDraggable = element;
  	    return paper;
  	  }
  	  else {
  	    return currentDraggable;
  	  }
  	};

  	paper.draggable.clearCurrent = function() {
  	  currentDraggable = null;
  	  return paper;
  	}

  	overrideElements(paper);
  	overrideSet(paper);
    return paper;
  };

  /* Raphael element extensions */
  function overrideElements(paper) {
    var elementTypes = ['circle', 'rect', 'ellipse', 'image', 'text', 'path'];
  	for(var i = 0; i < elementTypes.length; i++) {
  		overrideElementFunc(paper, elementTypes[i]);
  	}
  }

  function overrideElementFunc(paper, elementType) {
		var oldFunc = paper[elementType];
		paper[elementType] = function() {
			var element = oldFunc.apply(paper, arguments);
			element.draggable = new DraggableExtension(element);
			return element;
		};
	}

	/* Raphael set extensions */
	function overrideSet(paper) {
	  var oldFunc = paper.set;
	  paper.set = function() {
	    var set = oldFunc.apply(paper, arguments);
	    set.paper = paper;
	    set.draggable = new DraggableExtension(set);

	    overrideSetFunctions(set);
	    return set;
	  };
	}

	function overrideSetFunctions(set) {
	  var oldPushFunc = set.push;
	  set.push = function(element) {
	    oldPushFunc.apply(set, arguments);
	    if (element.draggable) {
	      element.draggable.parent = set;

	      if (set.draggable.enabled) {
	        element.draggable.enable();
	      }
	    }
	  }
	}

	/* Extension for objects that adds draggable functionality */
	var DraggableExtension = function (element) {
	  var paper = element.paper,
	      lastX,
	      lastY;

	  this.element = element;
	  this.enabled = false;

    element.mousedown(function(event) {
      if (paper.draggable.current() || !element.draggable.enabled)
        return;

      element.draggable.isDragging = true;

      paper.draggable.current(element);
      lastX = event.clientX;
  		lastY = event.clientY;
    });

    element.mousemove(function(event) {
      if (element.draggable.isDragging) {
        console.log(element.draggable.parent);
        var toMove = element.draggable.parent || element;

        var transX = event.clientX - lastX;
        var transY = event.clientY - lastY;

        toMove.translate(transX, transY);
        lastX = event.clientX;
        lastY = event.clientY;
      }
    });

	  element.mouseup(stopDragging);
	  element.mouseout(stopDragging);

	  function stopDragging() {
	    element.draggable.isDragging = false;
	    paper.draggable.clearCurrent();
	  }
	};

  DraggableExtension.prototype = {
    enable: function() {
      this.enabled = true;
      return this.element;
    },

    disable: function() {
      this.enabled = false;
      return this.element;
    }
  }
})();<|endoftext|>


<commit_before>'use strict';

var BabyParse = require('babyparse');

module.exports = {
  generateLanguageObject : generateLanguageObject,
  i18nScript : i18nScript
};

function generateLanguageObject(csv)
{
  var json = BabyParse.parse(csv).data;
  var languages = {};
  var headers = [];

  json[0].forEach(function(header)
  {
    if(i == 0 || !header.trim())
    {
      return;
    }

    languages[header] = {};
    headers.push(header);
  });

  for(var i = 1; i < json.length; i++)
  {
    if(!json[i][0].trim())
    {
      continue;
    }

    for(var j = 0; j < headers.length; j++)
    {
      languages[headers[j]][json[i][0]] = json[i][j + 1];
    }
  }

  return languages;
}

function i18nScript(lang)
{
  var imported = document.createElement('script');
  var fileImport = 'angular-locale_' + lang.toLowerCase().replace(/_/g, '-') + '.js';
  imported.src = 'https://code.angularjs.org/1.2.10/i18n/' + fileImport;
  imported.onload = function()
  {
    document.head.removeChild(imported);
  };
  document.head.appendChild(imported);
}
<commit_msg>fixed foreach<commit_after>'use strict';

var BabyParse = require('babyparse');

module.exports = {
  generateLanguageObject : generateLanguageObject,
  i18nScript : i18nScript
};

function generateLanguageObject(csv)
{
  var json = BabyParse.parse(csv).data;
  var languages = {};
  var headers = [];

  json[0].forEach(function(header, i)
  {
    if(i == 0 || !header.trim())
    {
      return;
    }

    languages[header] = {};
    headers.push(header);
  });

  for(var i = 1; i < json.length; i++)
  {
    if(!json[i][0].trim())
    {
      continue;
    }

    for(var j = 0; j < headers.length; j++)
    {
      languages[headers[j]][json[i][0]] = json[i][j + 1];
    }
  }

  return languages;
}

function i18nScript(lang)
{
  var imported = document.createElement('script');
  var fileImport = 'angular-locale_' + lang.toLowerCase().replace(/_/g, '-') + '.js';
  imported.src = 'https://code.angularjs.org/1.2.10/i18n/' + fileImport;
  imported.onload = function()
  {
    document.head.removeChild(imported);
  };
  document.head.appendChild(imported);
}
<|endoftext|>


<commit_before>var htmlparser = require('./vendor/htmlparser2')
var entities = require('./vendor/entities')
var React = require('react-native')
var {
  LinkingIOS,
  StyleSheet,
  Text,
} = React

var LINE_BREAK = '\n'
var PARAGRAPH_BREAK = '\n\n'

function htmlToElement(rawHtml, opts, done) {
  function domToElement(dom, parent) {
    if (!dom) return null

    return dom.map((node, index, list) => {
      if (opts.customRenderer) {
        var rendered = opts.customRenderer(node, index, list)
        if (rendered || rendered === null) return rendered
      }

      if (node.type == 'text') {
        return (
          <Text key={index} style={parent ? opts.styles[parent.name] : null}>
            {entities.decodeHTML(node.data)}
          </Text>
        )
      }

      if (node.type == 'tag') {
        var linkPressHandler = null
        if (node.name == 'a' && node.attribs && node.attribs.href) {
          linkPressHandler = () => opts.linkHandler(entities.decodeHTML(node.attribs.href))
        }

        return (
          <Text key={index} onPress={linkPressHandler}>
            {node.name == 'pre' ? LINE_BREAK : null}
            {domToElement(node.children, node)}
            {node.name == 'br' ? LINE_BREAK : null}
            {node.name == 'p' && index < list.length-1 ? PARAGRAPH_BREAK : null}
          </Text>
        )
      }
    })
  }

  var handler = new htmlparser.DomHandler(function (err, dom) {
    if (err) done(err)
    done(null, domToElement(dom))
  })
  var parser = new htmlparser.Parser(handler)
  parser.write(rawHtml)
  parser.done()
}

var HTMLView = React.createClass({
  mixins: [
    React.addons.PureRenderMixin,
  ],
  getDefaultProps() {
    return {
      onLinkPress: LinkingIOS.openURL,
    }
  },
  getInitialState() {
    return {
      element: null,
    }
  },
  componentWillReceiveProps() {
    if (this.state.element) return
    this.startHtmlRender()
  },
  componentDidMount() {
    this.startHtmlRender()
  },
  startHtmlRender() {
    if (!this.props.value) return
    if (this.renderingHtml) return

    var opts = {
      linkHandler: this.props.onLinkPress,
      styles: Object.assign({}, baseStyles, this.props.stylesheet),
      customRenderer: this.props.renderNode,
    }

    this.renderingHtml = true
    htmlToElement(this.props.value, opts, (err, element) => {
      this.renderingHtml = false

      if (err) return (this.props.onError || console.error)(err)

      if (this.isMounted()) this.setState({element})
    })
  },
  render() {
    if (this.state.element) {
      return <Text children={this.state.element} />
    }
    return <Text />
  }
})

var boldStyle = {fontWeight: '500'}
var italicStyle = {fontStyle: 'italic'}
var codeStyle = {fontFamily: 'Menlo'}

var baseStyles = StyleSheet.create({
  b: boldStyle,
  strong: boldStyle,
  i: italicStyle,
  em: italicStyle,
  pre: codeStyle,
  code: codeStyle,
  a: {
    fontWeight: '500',
    color: '#007AFF',
  },
})

module.exports = HTMLView
<commit_msg>Add support for LI elements with bullets<commit_after>var htmlparser = require('./vendor/htmlparser2')
var entities = require('./vendor/entities')
var React = require('react-native')
var {
  LinkingIOS,
  StyleSheet,
  Text,
} = React

var LINE_BREAK = '\n'
var PARAGRAPH_BREAK = '\n\n'
var BULLET = '  \u2022  '

function htmlToElement(rawHtml, opts, done) {
  function domToElement(dom, parent) {
    if (!dom) return null

    return dom.map((node, index, list) => {
      if (opts.customRenderer) {
        var rendered = opts.customRenderer(node, index, list)
        if (rendered || rendered === null) return rendered
      }

      if (node.type == 'text') {
        return (
          <Text key={index} style={parent ? opts.styles[parent.name] : null}>
            {entities.decodeHTML(node.data)}
          </Text>
        )
      }

      if (node.type == 'tag') {
        var linkPressHandler = null
        if (node.name == 'a' && node.attribs && node.attribs.href) {
          linkPressHandler = () => opts.linkHandler(entities.decodeHTML(node.attribs.href))
        }

        return (
          <Text key={index} onPress={linkPressHandler}>
            {node.name == 'pre' ? LINE_BREAK : null}
            {node.name == 'li' ? BULLET : null}
            {domToElement(node.children, node)}
            {node.name == 'br' ? LINE_BREAK : null}
            {node.name == 'li' ? LINE_BREAK : null}
            {node.name == 'p' && index < list.length-1 ? PARAGRAPH_BREAK : null}
          </Text>
        )
      }
    })
  }

  var handler = new htmlparser.DomHandler(function (err, dom) {
    if (err) done(err)
    done(null, domToElement(dom))
  })
  var parser = new htmlparser.Parser(handler)
  parser.write(rawHtml)
  parser.done()
}

var HTMLView = React.createClass({
  mixins: [
    React.addons.PureRenderMixin,
  ],
  getDefaultProps() {
    return {
      onLinkPress: LinkingIOS.openURL,
    }
  },
  getInitialState() {
    return {
      element: null,
    }
  },
  componentWillReceiveProps() {
    if (this.state.element) return
    this.startHtmlRender()
  },
  componentDidMount() {
    this.startHtmlRender()
  },
  startHtmlRender() {
    if (!this.props.value) return
    if (this.renderingHtml) return

    var opts = {
      linkHandler: this.props.onLinkPress,
      styles: Object.assign({}, baseStyles, this.props.stylesheet),
      customRenderer: this.props.renderNode,
    }

    this.renderingHtml = true
    htmlToElement(this.props.value, opts, (err, element) => {
      this.renderingHtml = false

      if (err) return (this.props.onError || console.error)(err)

      if (this.isMounted()) this.setState({element})
    })
  },
  render() {
    if (this.state.element) {
      return <Text children={this.state.element} />
    }
    return <Text />
  }
})

var boldStyle = {fontWeight: '500'}
var italicStyle = {fontStyle: 'italic'}
var codeStyle = {fontFamily: 'Menlo'}

var baseStyles = StyleSheet.create({
  b: boldStyle,
  strong: boldStyle,
  i: italicStyle,
  em: italicStyle,
  pre: codeStyle,
  code: codeStyle,
  a: {
    fontWeight: '500',
    color: '#007AFF',
  },
})

module.exports = HTMLView
<|endoftext|>


<commit_before>/* This module uses a give ip to visit the whole urls in the bucket assigned to it
*/
'use strict';

module.exports = function(bucketExplorer , database) {
	//
    var ObjectId = require('mongodb').ObjectId;
	var Spooky = require('spooky');
    var EventEmitter = require('events').EventEmitter;

	var bucket;
    var ipQueue = [];
    var ipQueueIndex = 0;

    var exitFlag = false;

    //
    var Buckets = database.model('Buckets');

    //
    var setBucket =  function(bucketObj){
    	 bucket = bucketObj;
         if(bucket.urls.length>0){
            for(var i=0; i<bucket.urls.length; i++){
                bucket.urls[i].visited = 0;
                bucket.urls[i].statusText = 'No status yet';
            };
         }

    	 console.log(bucket);
    	 console.log('bucket set in visitor');
         startUpdateDaemon();
    };

    //
    var getBucket = function(){
    	return bucket;
    };

    //
    var updateBucket = function(bucketObj , meta){
    	console.log(meta);
    	if(bucket){
            if(bucketObj._id+'' == bucket._id){
	           if(meta.action == 0){
	           	   console.log('Deleting url in this bucket');
                   bucket.urls.splice(meta.index , 1);
	           }
	           else if(meta.action == 1) {
                   console.log('Updating url in this bucket');
                   bucket = bucketObj;
	           }
	    	}
	    	else{
	    	   console.log('Just passing by');
	    	}
    	}
    	else{
    		 console.log('Bucket empty here');
    	}
    }

    //
    var notifyDelete = function(_id){
         console.log('Bucket with'+ _id +'deleted');
         if(bucket){
             if(bucket._id == _id){
                console.log('This bucket was deleted');
                setTimeout(function(){
                    process.exit(0);
                } , 10000);
             }
             else{
                 console.log('Not this one 1..');
             }

         }
         else{
            console.log('Not this one..');
         }
    }

    //
    var visitWith = function(ip){
        console.log('Adding ip to queue');
        if(ipQueue.indexOf(ip)<0){
            ipQueue.push(ip);
        }
        else{
            console.log('ip alredy used or currently in use');
        }

        if(ipQueue.length == 1){
            startVisitingDaemon();
        }
    }

    //
    var exitWhenDone = function(){
         exitFlag = true;
    }

    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    var V_WORKER = function(){
        var workerEvents = new EventEmitter;

        var visit = function(ip , urlsArr){
            console.log('visit starting with '+ip+' and '+urlsArr.length+' urls');

            var spooky = new Spooky({
                child: {
                    transport: 'http',
                    proxy: ip
                },
                casper: {
                    logLevel: 'debug',
                    verbose: true,
                    options: {
                    pageSettings: {
                            //Referer: 'http://www.palingram.com'
                        }
                    }
                }
            },
            function(err){
            if (err) {
                    e = new Error('Failed to initialize SpookyJS');
                    e.details = err;
                    throw e;
                }

                var worker = function(url , selector , index){
                    //
                    spooky.start(url);
                    spooky.then([{url:url , selector:selector , urlIndex:index} , function(){
                        //General case
                        this.done = function(err , status){
                            if(err){
                                this.emit('done', {status:this.getCurrentUrl() , index:urlIndex});
                                return phantom.clearCookies();
                            }
                            else{
                                this.emit('done', {status:this.getCurrentUrl() , index:urlIndex});
                                return phantom.clearCookies();
                            }
                        }

                        if(selector=='none'){
                            this.then(function(){
                                this.wait(15000 , function(){
                                    this.done(null , true);
                                });
                            });
                        }

                        else{
                            this.then(function(){
                                this.waitForSelector(selector , function(){
                                    this.thenClick(selector , function() {
                                       this.done();
                                    });

                                } , function(){
                                      this.done(true , null);
                                } , 20000);
                            });
                        }
                    }]);
                }

                //
                for(var i=0; i < urlsArr.length; i++){
                    worker(urlsArr[i].urlName , urlsArr[i].selector , i);
                }

                //
                spooky.run();
            });

            //
            spooky.on('console', function (line) {
                console.log(line);
            });

            //
            spooky.on('error', function (e, stack) {
                console.error(stack||e);
                workerEvents.emit('done' , {status:stack?stack:e , index:0});
            });

            //
            spooky.on('done', function (status) {
                workerEvents.emit('done' , status);
            });

            //self destruct this instance in 15 minutes
            setTimeout(function(){
                console.log('Instance destroyed');
                spooky.destroy();
            } , 15*60000);
        };

        return {
            visit:visit,
            status:workerEvents
        }
    }
    //++++++++++++++++++++++++++++++++END++++++++++++++++++++++++++++++++++++++++++

    //updateBucket after every 100 secs of activity
    function startUpdateDaemon(){
        var timer = 0;
        console.log('Starting cron daemon');
        setInterval(function(){
            bucket.lastActive = Date.now()+'';
            Buckets.update(
                {_id : ObjectId(bucket._id)},
                bucket,
                function(err , result){
                    if(err){
                        console.log(err);
                        res.status(500).send('Database error during cron update in visitor');
                    }
                    else {
                        timer+=2;
                        if(timer >= 162){
                            console.log('Maximum uptime of two hours exceeded exiting....');
                            process.exit(0);
                        }
                        console.log('bucket updated in cron job');
                    }
                }
          );
        } , 120000);
    }

    //
    function startVisitingDaemon(){
       let visiting = 0;
       let limit =  20;
       let v_worker = V_WORKER();

       setInterval(function(){
          if(limit > visiting && ipQueueIndex < ipQueue.length){
               v_worker.visit(ipQueue[ipQueueIndex] , bucket.urls);
               ipQueueIndex++;
               visiting+= bucket.urls.length;
          }
          else{
              if(limit < visiting){
                  console.log('Visiting urls have exceeded the limit');
                  limit+=100;
              }
              else{
                  //console.log('Good ip shortage');
              }

          }

       } , 10000);

       //
       v_worker.status.on('done' , function(status){
            console.log(status);
            bucket.urls[status.index].statusText = status.status;
            bucket.urls[status.index].visited++;
            visiting--;

            if(exitFlag && ipQueueIndex >= ipQueue.length){
                console.log('All ips have been visited exiting process...');
                process.exit(0);
            }
        });
    }

	return {
	    visitWith:visitWith,
	    getBucket:getBucket,
	    setBucket:setBucket,
	    updateBucket:updateBucket,
        notifyDelete:notifyDelete,
	    exitWhenDone:exitWhenDone
	}
};<commit_msg>updated<commit_after>/* This module uses a give ip to visit the whole urls in the bucket assigned to it
*/
'use strict';

module.exports = function(bucketExplorer , database) {
	//
    var ObjectId = require('mongodb').ObjectId;
	var Spooky = require('spooky');
    var EventEmitter = require('events').EventEmitter;

	var bucket;
    var ipQueue = [];
    var ipQueueIndex = 0;

    var exitFlag = false;

    //
    var Buckets = database.model('Buckets');

    //
    var setBucket =  function(bucketObj){
    	 bucket = bucketObj;
         if(bucket.urls.length>0){
            for(var i=0; i<bucket.urls.length; i++){
                bucket.urls[i].visited = 0;
                bucket.urls[i].statusText = 'No status yet';
            };
         }

    	 console.log(bucket);
    	 console.log('bucket set in visitor');
         startUpdateDaemon();
    };

    //
    var getBucket = function(){
    	return bucket;
    };

    //
    var updateBucket = function(bucketObj , meta){
    	console.log(meta);
    	if(bucket){
            if(bucketObj._id+'' == bucket._id){
	           if(meta.action == 0){
	           	   console.log('Deleting url in this bucket');
                   bucket.urls.splice(meta.index , 1);
	           }
	           else if(meta.action == 1) {
                   console.log('Updating url in this bucket');
                   bucket = bucketObj;
	           }
	    	}
	    	else{
	    	   console.log('Just passing by');
	    	}
    	}
    	else{
    		 console.log('Bucket empty here');
    	}
    }

    //
    var notifyDelete = function(_id){
         console.log('Bucket with'+ _id +'deleted');
         if(bucket){
             if(bucket._id == _id){
                console.log('This bucket was deleted');
                setTimeout(function(){
                    process.exit(0);
                } , 10000);
             }
             else{
                 console.log('Not this one 1..');
             }

         }
         else{
            console.log('Not this one..');
         }
    }

    //
    var visitWith = function(ip){
        console.log('Adding ip to queue');
        if(ipQueue.indexOf(ip)<0){
            ipQueue.push(ip);
        }
        else{
            console.log('ip alredy used or currently in use');
        }

        if(ipQueue.length == 1){
            startVisitingDaemon();
        }
    }

    //
    var exitWhenDone = function(){
         exitFlag = true;
    }

    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    var V_WORKER = function(){
        var workerEvents = new EventEmitter;

        var visit = function(ip , urlsArr){
            console.log('visit starting with '+ip+' and '+urlsArr.length+' urls');

            var spooky = new Spooky({
                child: {
                    transport: 'http',
                    proxy: ip
                },
                casper: {
                    logLevel: 'debug',
                    verbose: true,
                    options: {
                    pageSettings: {
                            //Referer: 'http://www.palingram.com'
                        }
                    }
                }
            },
            function(err){
            if (err) {
                    e = new Error('Failed to initialize SpookyJS');
                    e.details = err;
                    throw e;
                }

                var worker = function(url , selector , index){
                    //
                    spooky.start(url);
                    spooky.then([{url:url , selector:selector , urlIndex:index} , function(){
                        //General case
                        this.done = function(err , status){
                            if(err){
                                this.emit('done', {status:this.getCurrentUrl() , index:urlIndex});
                                return phantom.clearCookies();
                            }
                            else{
                                this.emit('done', {status:this.getCurrentUrl() , index:urlIndex});
                                return phantom.clearCookies();
                            }
                        }

                        if(selector=='none'){
                            this.then(function(){
                                this.wait(15000 , function(){
                                    this.done(null , true);
                                });
                            });
                        }

                        else{
                            this.then(function(){
                                this.waitForSelector(selector , function(){
                                    this.thenClick(selector , function() {
                                       this.done();
                                    });

                                } , function(){
                                      this.done(true , null);
                                } , 20000);
                            });
                        }
                    }]);
                }

                //
                for(var i=0; i < urlsArr.length; i++){
                    worker(urlsArr[i].urlName , urlsArr[i].selector , i);
                }

                //
                spooky.run();
            });

            //
            spooky.on('console', function (line) {
                console.log(line);
            });

            //
            spooky.on('error', function (e, stack) {
                console.error(stack||e);
                workerEvents.emit('done' , {status:stack?stack:e , index:0});
            });

            //
            spooky.on('done', function (status) {
                workerEvents.emit('done' , status);
            });

            //self destruct this instance in 15 minutes
            setTimeout(function(){
                console.log('Instance destroyed');
                spooky.destroy();
            } , 15*60000);
        };

        return {
            visit:visit,
            status:workerEvents
        }
    }
    //++++++++++++++++++++++++++++++++END++++++++++++++++++++++++++++++++++++++++++

    //updateBucket after every 100 secs of activity
    function startUpdateDaemon(){
        var timer = 0;
        console.log('Starting cron daemon');
        setInterval(function(){
            bucket.lastActive = Date.now()+'';
            Buckets.update(
                {_id : ObjectId(bucket._id)},
                bucket,
                function(err , result){
                    if(err){
                        console.log(err);
                        res.status(500).send('Database error during cron update in visitor');
                    }
                    else {
                        timer+=2;
                        if(timer >= 162){
                            console.log('Maximum uptime of two hours exceeded exiting....');
                            process.exit(0);
                        }
                        console.log('bucket updated in cron job');
                    }
                }
          );
        } , 120000);
    }

    //
    function startVisitingDaemon(){
       let visiting = 0;
       let limit =  20;
       let v_worker = V_WORKER();

       setInterval(function(){
          if(limit > visiting && ipQueueIndex < ipQueue.length){
               v_worker.visit(ipQueue[ipQueueIndex] , bucket.urls);
               ipQueueIndex++;
               visiting+= bucket.urls.length;
          }
          else{
              if(limit < visiting){
                  console.log('Visiting urls have exceeded the limit');
                  limit+=100;
              }
              else{
                  //console.log('Good ip shortage');
              }

          }

       } , 10000);

       //
       v_worker.status.on('done' , function(status){
            console.log(status);
            bucket.urls[status.index].statusText = status.status;
            bucket.urls[status.index].visited++;
            visiting--;
            limit--;

            if(exitFlag && ipQueueIndex >= ipQueue.length){
                console.log('All ips have been visited exiting process...');
                process.exit(0);
            }
        });
    }

	return {
	    visitWith:visitWith,
	    getBucket:getBucket,
	    setBucket:setBucket,
	    updateBucket:updateBucket,
        notifyDelete:notifyDelete,
	    exitWhenDone:exitWhenDone
	}
};<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import {remote, shell} from 'electron';
import {TextBuffer} from 'atom';
import AtomTextEditor from '../atom/atom-text-editor';
import RefHolder from '../models/ref-holder';
import {addEvent} from '../reporter-proxy';
const {Menu, MenuItem} = remote;

export default class ActionableReviewView extends React.Component {
  static propTypes = {
    originalContent: PropTypes.object.isRequired,
    isPosting: PropTypes.bool,
    confirm: PropTypes.func.isRequired,
    contentUpdater: PropTypes.func.isRequired,
    render: PropTypes.func.isRequired,
  }

  constructor(props) {
    super(props);
    this.refEditor = new RefHolder();
    this.state = {editing: false};
  }

  render() {
    return this.state.editing ? this.renderEditor() : this.props.render(this.showActionsMenu);
  }

  renderEditor() {
    const buffer = new TextBuffer();
    buffer.setText(this.props.originalContent.body);

    return (

        <AtomTextEditor
          buffer={buffer}
          lineNumberGutterVisible={false}
          softWrapped={true}
          autoHeight={true}
          readOnly={this.props.isPosting}
          refModel={this.refEditor}
        />

          <button
            className="github-Review-editableCancelButton btn btn-sm"
            title="Cancel editing comment"
            disabled={this.props.isPosting}
            onClick={() => this.onCancel(buffer.getText() || '')}>
            Cancel
          </button>
          <button
            className="github-Review-updateCommentButton btn btn-sm btn-primary"
            title="Update comment"
            disabled={this.props.isPosting}
            onClick={() => this.onSubmitUpdate(buffer.getText() || '')}>
            Update comment
          </button>

    );
  }

  onCancel = text => {
    if (text === this.props.originalContent.body) {
      this.setState({editing: false});
    } else {
      const choice = this.props.confirm({
        message: 'Are you sure you want to discard your unsaved changes?',
        buttons: ['OK', 'Cancel'],
      });
      if (choice === 0) {
        this.setState({editing: false});
      }
    }
  }

  onSubmitUpdate = text => {
    if (text === this.props.originalContent.body || text === '') {
      this.setState({editing: false});
      return;
    }
    const didUpdateComment = () => this.setState({editing: false});
    this.props.contentUpdater(this.props.originalContent.id, text, didUpdateComment);
  }

  reportAbuse = (commentUrl, author) => {
    return new Promise((resolve, reject) => {
      const url = 'https://github.com/contact/report-content?report=' +
        `${encodeURIComponent(author)}&content_url=${encodeURIComponent(commentUrl)}`;
      shell.openExternal(url, {}, err => {
        if (err) { reject(err); } else {
          resolve();
          addEvent('report-abuse', {package: 'github', component: this.constructor.name});
        }
      });
    });
  }

  openOnGitHub = url => {
    return new Promise((resolve, reject) => {
      shell.openExternal(url, {}, err => {
        if (err) { reject(err); } else {
          resolve();
          addEvent('open-comment-in-browser', {package: 'github', component: this.constructor.name});
        }
      });
    });
  }

  showActionsMenu = (event, comment, author) => {
    event.preventDefault();

    const menu = new Menu();

    menu.append(new MenuItem({
      label: 'Edit',
      click: () => this.setState({editing: true}),
    }));

    menu.append(new MenuItem({
      label: 'Open on GitHub',
      click: () => this.openOnGitHub(comment.url),
    }));

    menu.append(new MenuItem({
      label: 'Report abuse',
      click: () => this.reportAbuse(comment.url, author.login),
    }));

    menu.popup(remote.getCurrentWindow());
  }
}
<commit_msg>only show edit option if user has the right permission<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import {remote, shell} from 'electron';
import {TextBuffer} from 'atom';
import AtomTextEditor from '../atom/atom-text-editor';
import RefHolder from '../models/ref-holder';
import {addEvent} from '../reporter-proxy';
const {Menu, MenuItem} = remote;

export default class ActionableReviewView extends React.Component {
  static propTypes = {
    originalContent: PropTypes.object.isRequired,
    isPosting: PropTypes.bool,
    confirm: PropTypes.func.isRequired,
    contentUpdater: PropTypes.func.isRequired,
    render: PropTypes.func.isRequired,
  }

  constructor(props) {
    super(props);
    this.refEditor = new RefHolder();
    this.state = {editing: false};
  }

  render() {
    return this.state.editing ? this.renderEditor() : this.props.render(this.showActionsMenu);
  }

  renderEditor() {
    const buffer = new TextBuffer();
    buffer.setText(this.props.originalContent.body);

    return (

        <AtomTextEditor
          buffer={buffer}
          lineNumberGutterVisible={false}
          softWrapped={true}
          autoHeight={true}
          readOnly={this.props.isPosting}
          refModel={this.refEditor}
        />

          <button
            className="github-Review-editableCancelButton btn btn-sm"
            title="Cancel editing comment"
            disabled={this.props.isPosting}
            onClick={() => this.onCancel(buffer.getText() || '')}>
            Cancel
          </button>
          <button
            className="github-Review-updateCommentButton btn btn-sm btn-primary"
            title="Update comment"
            disabled={this.props.isPosting}
            onClick={() => this.onSubmitUpdate(buffer.getText() || '')}>
            Update comment
          </button>

    );
  }

  onCancel = text => {
    if (text === this.props.originalContent.body) {
      this.setState({editing: false});
    } else {
      const choice = this.props.confirm({
        message: 'Are you sure you want to discard your unsaved changes?',
        buttons: ['OK', 'Cancel'],
      });
      if (choice === 0) {
        this.setState({editing: false});
      }
    }
  }

  onSubmitUpdate = text => {
    if (text === this.props.originalContent.body || text === '') {
      this.setState({editing: false});
      return;
    }
    const didUpdateComment = () => this.setState({editing: false});
    this.props.contentUpdater(this.props.originalContent.id, text, didUpdateComment);
  }

  reportAbuse = (commentUrl, author) => {
    return new Promise((resolve, reject) => {
      const url = 'https://github.com/contact/report-content?report=' +
        `${encodeURIComponent(author)}&content_url=${encodeURIComponent(commentUrl)}`;
      shell.openExternal(url, {}, err => {
        if (err) { reject(err); } else {
          resolve();
          addEvent('report-abuse', {package: 'github', component: this.constructor.name});
        }
      });
    });
  }

  openOnGitHub = url => {
    return new Promise((resolve, reject) => {
      shell.openExternal(url, {}, err => {
        if (err) { reject(err); } else {
          resolve();
          addEvent('open-comment-in-browser', {package: 'github', component: this.constructor.name});
        }
      });
    });
  }

  showActionsMenu = (event, content, author) => {
    event.preventDefault();

    const menu = new Menu();

    if (content.viewerCanUpdate) {
      menu.append(new MenuItem({
        label: 'Edit',
        click: () => this.setState({editing: true}),
      }));
    }

    menu.append(new MenuItem({
      label: 'Open on GitHub',
      click: () => this.openOnGitHub(content.url),
    }));

    menu.append(new MenuItem({
      label: 'Report abuse',
      click: () => this.reportAbuse(content.url, author.login),
    }));

    menu.popup(remote.getCurrentWindow());
  }
}
<|endoftext|>


<commit_before>var AppMenus = {

	menus: Object.create(null),
	items: Object.create(null),

	init: function() {
		this._createMenuBar();
	},

	_createFileMenu: function(file, type, line) {
		var items = Object.create(null);

		items['stage'] = new gui.MenuItem({
			label: 'Stage file',
			icon: 'icons/stage.png',
			click: function() {
				commander.stageFile(file, _handleGitResponse);
			}
		});
		items['unstage'] = new gui.MenuItem({
			label: 'Unstage file',
			icon: 'icons/unstage.png',
			click: function() {
				commander.unstageFile(file, _handleGitResponse);
			}
		});
		items['stageHunk'] = new gui.MenuItem({
			label: 'Stage hunk',
			icon: 'icons/stage.png',
			click: function() {
				commander.stageHunk(file, line, _handleGitResponse);
			}
		});
		items['unstageHunk'] = new gui.MenuItem({
			label: 'Unstage hunk',
			icon: 'icons/unstage.png',
			click: function() {
				commander.unstageHunk(file, line, _handleGitResponse);
			}
		});
		items['revert'] = new gui.MenuItem({
			label: 'Revert changes',
			icon: 'icons/revert.png',
			click: function() {
				commander.revertFile(file, _handleGitResponse);
			}
		});
		items['submoduleUpdate'] = new gui.MenuItem({
			label: 'Submodule update',
			icon: 'icons/submodule-update.png',
			click: function() {
				commander.submoduleUpdate(file, _handleGitResponse);
			}
		});
		items['checkoutTheirs'] = new gui.MenuItem({
			label: 'Checkout theirs',
			icon: 'icons/checkout-theirs.png',
			click: function() {
				commander.checkoutTheirs(file, _handleGitResponse);
			}
		});
		items['checkoutOurs'] = new gui.MenuItem({
			label: 'Checkout ours',
			icon: 'icons/checkout-ours.png',
			click: function() {
				commander.checkoutOurs(file, _handleGitResponse);
			}
		});
		items['open'] = new gui.MenuItem({
			label: 'Open file',
			icon: 'icons/open-file.png',
			click: function() {
				External.openFile(file);
			}
		});
		items['delete'] = new gui.MenuItem({
			label: 'Delete file',
			icon: 'icons/delete.png',
			click: function() {
				commander.removeFileFromDisk(file, _handleGitResponse);
			}
		});
		items['viewHistory'] = new gui.MenuItem({
			label: 'View file history',
			icon: 'icons/view-history.png',
			click: function() {
				External.openGitk(currentModulePath, file);
			}
		});
		items['blame'] = new gui.MenuItem({
			label: 'Blame',
			icon: 'icons/view-history.png',
			click: function() {
				External.openGitBlame(currentModulePath, file, line);
			}
		});
		items['stats'] = new gui.MenuItem({
			label: 'Statistics',
			icon: 'icons/statistics.png',
			click: function() {
				FileStatisticsDialog(file);
			}
		});

		var isUnstagedNew = type === 'unstaged' && file.unstaged && file.status === 'new';
		var isDeleted = file.status === 'deleted';
		var isSubmodule = file.type === 'submodule';
		var menu = new gui.Menu();

		if (type === 'unstaged' && file.unstaged) menu.append(items['stage']);
		if (type === 'staged' && file.staged) menu.append(items['unstage']);
		if (line !== null && type === 'unstaged' && file.unstaged && !isSubmodule && file.status === 'modified') menu.append(items['stageHunk']);
		if (line !== null && type === 'staged' && file.staged && !isSubmodule && file.status === 'modified') menu.append(items['unstageHunk']);
		if (file.unmerged) {
			menu.append(items['checkoutTheirs']);
			menu.append(items['checkoutOurs']);
		}
		if (isSubmodule && file.status === 'modified') menu.append(items['submoduleUpdate']);
		if (file.status !== 'new' && !isSubmodule) menu.append(items['revert']);
		if (file.status !== 'deleted') menu.append(items['open']);
		if (type === 'unstaged' && file.unstaged && !isDeleted) menu.append(items['delete']);
		if (!isUnstagedNew && !isDeleted) {
			menu.append(new gui.MenuItem({type: 'separator'}));
			if (file.status !== 'new') menu.append(items['viewHistory']);
			if (!isSubmodule) menu.append(items['blame']);
			menu.append(items['stats']);
		}

		return menu;
	},

	_createMenuBar: function() {
		// Repository items
		this.items['repositoryOpen'] = new gui.MenuItem({
			label: 'Open...',
			click: chooseRepository
		});
		this.items['repositoryClose'] = new gui.MenuItem({
			label: 'Close',
            enabled: false,
			click: closeRepository
		});
		this.items['repositorySubmoduleUpdate'] = new gui.MenuItem({
			label: 'Submodule update',
			click: function() {
				commander.submoduleUpdate(null, _handleGitResponse);
			}
		});
		this.items['repositoryExplore'] = new gui.MenuItem({
			label: 'Explore...',
			click: function() {
				gui.Shell.openItem(currentModulePath);
			}
		});
		this.items['repositoryBrowse'] = new gui.MenuItem({
			label: 'View branch history (gitk)',
			enabled: false,
			click: function() {
				External.openGitk(currentModulePath);
			}
		});
		this.items['repositoryRefresh'] = new gui.MenuItem({
			label: 'Refresh',
			enabled: false,
			click: updateCurrentModuleStatus
		});
		this.items['repositoryQuit'] = new gui.MenuItem({
			label: 'Quit',
			click: function() {
				gui.Window.get().close();
			}
		});

		// Branch items
		this.items['branchCheckout'] = new gui.MenuItem({
			label: 'Checkout...',
			click: function() {
				BranchCheckoutDialog();
			}
		});
		this.items['branchCreate'] = new gui.MenuItem({
			label: 'Create...',
			click: function() {
				BranchCreateDialog();
			}
		});

		// Stash items
		this.items['stashSave'] = new gui.MenuItem({
			label: 'Save',
			click: function() {
				commander.stashSave(_handleGitResponse);
			}
		});
		this.items['stashPop'] = new gui.MenuItem({
			label: 'Pop',
			click: function() {
				commander.stashPop(_handleGitResponse);
			}
		});

		// Options items
		this.items['optionsZoomIn'] = new gui.MenuItem({
			label: 'Zoom In',
			click: function() {
				try {
					var zoom = gui.Window.get().zoomLevel;
					gui.Window.get().zoomLevel = [zoom + 1]; // FIXME: this should not be an array! #node-webkit
				} catch (e) {}
			}
		});
		this.items['optionsZoomOut'] = new gui.MenuItem({
			label: 'Zoom Out',
			click: function() {
				try {
					var zoom = gui.Window.get().zoomLevel;
					gui.Window.get().zoomLevel = [zoom - 1]; // FIXME: this should not be an array! #node-webkit
				} catch (e) {}
			}
		});
		this.items['optionsLessContext'] = new gui.MenuItem({
			label: 'Less context',
			click: function() {
				config.diff.contextLines--;
				if (config.diff.contextLines < 2) config.diff.contextLines = 2;
				updateGlobalStatus();
			}
		});
		this.items['optionsMoreContext'] = new gui.MenuItem({
			label: 'More context',
			click: function() {
				config.diff.contextLines++;
				updateGlobalStatus();
			}
		});
		this.items['optionsEolWhitespace'] = new gui.MenuItem({
			type: 'checkbox',
			checked: config.diff.ignoreEolWhitespace,
			label: 'Ignore EOL whitespace',
			click: function() {
				config.diff.ignoreEolWhitespace = this.checked;
				updateGlobalStatus();
			}
		});
		this.items['optionsSyntaxHighlighting'] = new gui.MenuItem({
			type: 'checkbox',
			checked: config.diff.highlight.enabled,
			label: 'Syntax highlighting',
			click: function() {
				config.diff.highlight.enabled = this.checked;
				updateGlobalStatus();
			}
		});

		// Help items
		this.items['helpReportBugs'] = new gui.MenuItem({
			label: 'Report bug...',
			click: function() {
				gui.Shell.openExternal('https://github.com/demian85/git-watcher/issues');
			}
		});

		this.menus.repository = new gui.Menu();
		this.menus.repository.append(this.items['repositoryOpen']);
		this.menus.repository.append(this.items['repositoryClose']);
		this.menus.repository.append(this.items['repositoryRefresh']);
		this.menus.repository.append(new gui.MenuItem({type: 'separator'}));
		this.menus.repository.append(this.items['repositorySubmoduleUpdate']);
		this.menus.repository.append(this.items['repositoryExplore']);
		this.menus.repository.append(this.items['repositoryBrowse']);
		this.menus.repository.append(new gui.MenuItem({type: 'separator'}));
		this.menus.repository.append(this.items['repositoryQuit']);

		this.menus.branch = new gui.Menu();
		this.menus.branch.append(this.items['branchCheckout']);
		this.menus.branch.append(this.items['branchCreate']);

		this.menus.stash = new gui.Menu();
		this.menus.stash.append(this.items['stashSave']);
		this.menus.stash.append(this.items['stashPop']);

		this.menus.options = new gui.Menu();
		this.menus.options.append(this.items['optionsZoomIn']);
		this.menus.options.append(this.items['optionsZoomOut']);
		this.menus.options.append(new gui.MenuItem({type: 'separator'}));
		this.menus.options.append(this.items['optionsLessContext']);
		this.menus.options.append(this.items['optionsMoreContext']);
		this.menus.options.append(this.items['optionsEolWhitespace']);
		this.menus.options.append(this.items['optionsSyntaxHighlighting']);

		this._createToolsMenu();

		this.menus.help = new gui.Menu();
		this.menus.help.append(this.items['helpReportBugs']);

		this.items['branchMenu'] = new gui.MenuItem({
			label: 'Branch',
			submenu: this.menus.branch
		});
		this.items['stashMenu'] = new gui.MenuItem({
			label: 'Stash',
			submenu: this.menus.stash
		});
		this.items['optionsMenu'] = new gui.MenuItem({
			label: 'Options',
			enabled: false,
			submenu: this.menus.options
		});
		this.items['toolsMenu'] = new gui.MenuItem({
			label: 'Tools',
			submenu: this.menus.tools
		});

		this.menubar = new gui.Menu({type: 'menubar'});
		this.menubar.append(new gui.MenuItem({
			label: 'Repository',
			submenu: this.menus.repository
		}));
		this.menubar.append(this.items['branchMenu']);
		this.menubar.append(this.items['stashMenu']);
		this.menubar.append(this.items['optionsMenu']);
		this.menubar.append(this.items['toolsMenu']);
		this.menubar.append(new gui.MenuItem({
			label: 'Help',
			submenu: this.menus.help
		}));

		gui.Window.get().menu = this.menubar;
	},

	_createToolsMenu: function() {
		this.menus.tools = new gui.Menu();
		config.tools.forEach(function(tool) {
			if (!tool.name || !tool.cmd) return;
			this.menus.tools.append(new gui.MenuItem({
				label: tool.name,
				click: function() {
					External.execTool(currentModulePath, tool);
				}
			}));
		}, this);
	},

	enableRepoMenu: function(enabled) {
		this.items['repositoryClose'].enabled = enabled;
		this.items['repositoryExplore'].enabled = enabled;
		this.items['repositoryBrowse'].enabled = enabled;
		this.items['repositoryRefresh'].enabled = enabled;
		this.items['branchMenu'].enabled = enabled;
		this.items['stashMenu'].enabled = enabled;
		this.items['optionsMenu'].enabled = enabled;
		this.items['toolsMenu'].enabled = enabled;
	},

	showFileListMenu: function(file, type, x, y, line) {
		var menu = this._createFileMenu(file, type, line);
		menu.popup(x, y);
	}
};
<commit_msg>Correctly toggle menu item state<commit_after>var AppMenus = {

	menus: Object.create(null),
	items: Object.create(null),

	init: function() {
		this._createMenuBar();
	},

	_createFileMenu: function(file, type, line) {
		var items = Object.create(null);

		items['stage'] = new gui.MenuItem({
			label: 'Stage file',
			icon: 'icons/stage.png',
			click: function() {
				commander.stageFile(file, _handleGitResponse);
			}
		});
		items['unstage'] = new gui.MenuItem({
			label: 'Unstage file',
			icon: 'icons/unstage.png',
			click: function() {
				commander.unstageFile(file, _handleGitResponse);
			}
		});
		items['stageHunk'] = new gui.MenuItem({
			label: 'Stage hunk',
			icon: 'icons/stage.png',
			click: function() {
				commander.stageHunk(file, line, _handleGitResponse);
			}
		});
		items['unstageHunk'] = new gui.MenuItem({
			label: 'Unstage hunk',
			icon: 'icons/unstage.png',
			click: function() {
				commander.unstageHunk(file, line, _handleGitResponse);
			}
		});
		items['revert'] = new gui.MenuItem({
			label: 'Revert changes',
			icon: 'icons/revert.png',
			click: function() {
				commander.revertFile(file, _handleGitResponse);
			}
		});
		items['submoduleUpdate'] = new gui.MenuItem({
			label: 'Submodule update',
			icon: 'icons/submodule-update.png',
			click: function() {
				commander.submoduleUpdate(file, _handleGitResponse);
			}
		});
		items['checkoutTheirs'] = new gui.MenuItem({
			label: 'Checkout theirs',
			icon: 'icons/checkout-theirs.png',
			click: function() {
				commander.checkoutTheirs(file, _handleGitResponse);
			}
		});
		items['checkoutOurs'] = new gui.MenuItem({
			label: 'Checkout ours',
			icon: 'icons/checkout-ours.png',
			click: function() {
				commander.checkoutOurs(file, _handleGitResponse);
			}
		});
		items['open'] = new gui.MenuItem({
			label: 'Open file',
			icon: 'icons/open-file.png',
			click: function() {
				External.openFile(file);
			}
		});
		items['delete'] = new gui.MenuItem({
			label: 'Delete file',
			icon: 'icons/delete.png',
			click: function() {
				commander.removeFileFromDisk(file, _handleGitResponse);
			}
		});
		items['viewHistory'] = new gui.MenuItem({
			label: 'View file history',
			icon: 'icons/view-history.png',
			click: function() {
				External.openGitk(currentModulePath, file);
			}
		});
		items['blame'] = new gui.MenuItem({
			label: 'Blame',
			icon: 'icons/view-history.png',
			click: function() {
				External.openGitBlame(currentModulePath, file, line);
			}
		});
		items['stats'] = new gui.MenuItem({
			label: 'Statistics',
			icon: 'icons/statistics.png',
			click: function() {
				FileStatisticsDialog(file);
			}
		});

		var isUnstagedNew = type === 'unstaged' && file.unstaged && file.status === 'new';
		var isDeleted = file.status === 'deleted';
		var isSubmodule = file.type === 'submodule';
		var menu = new gui.Menu();

		if (type === 'unstaged' && file.unstaged) menu.append(items['stage']);
		if (type === 'staged' && file.staged) menu.append(items['unstage']);
		if (line !== null && type === 'unstaged' && file.unstaged && !isSubmodule && file.status === 'modified') menu.append(items['stageHunk']);
		if (line !== null && type === 'staged' && file.staged && !isSubmodule && file.status === 'modified') menu.append(items['unstageHunk']);
		if (file.unmerged) {
			menu.append(items['checkoutTheirs']);
			menu.append(items['checkoutOurs']);
		}
		if (isSubmodule && file.status === 'modified') menu.append(items['submoduleUpdate']);
		if (file.status !== 'new' && !isSubmodule) menu.append(items['revert']);
		if (file.status !== 'deleted') menu.append(items['open']);
		if (type === 'unstaged' && file.unstaged && !isDeleted) menu.append(items['delete']);
		if (!isUnstagedNew && !isDeleted) {
			menu.append(new gui.MenuItem({type: 'separator'}));
			if (file.status !== 'new') menu.append(items['viewHistory']);
			if (!isSubmodule) menu.append(items['blame']);
			menu.append(items['stats']);
		}

		return menu;
	},

	_createMenuBar: function() {
		// Repository items
		this.items['repositoryOpen'] = new gui.MenuItem({
			label: 'Open...',
			click: chooseRepository
		});
		this.items['repositoryClose'] = new gui.MenuItem({
			label: 'Close',
            enabled: false,
			click: closeRepository
		});
		this.items['repositorySubmoduleUpdate'] = new gui.MenuItem({
			label: 'Submodule update',
			click: function() {
				commander.submoduleUpdate(null, _handleGitResponse);
			}
		});
		this.items['repositoryExplore'] = new gui.MenuItem({
			label: 'Explore...',
			click: function() {
				gui.Shell.openItem(currentModulePath);
			}
		});
		this.items['repositoryBrowse'] = new gui.MenuItem({
			label: 'View branch history (gitk)',
			enabled: false,
			click: function() {
				External.openGitk(currentModulePath);
			}
		});
		this.items['repositoryRefresh'] = new gui.MenuItem({
			label: 'Refresh',
			enabled: false,
			click: updateCurrentModuleStatus
		});
		this.items['repositoryQuit'] = new gui.MenuItem({
			label: 'Quit',
			click: function() {
				gui.Window.get().close();
			}
		});

		// Branch items
		this.items['branchCheckout'] = new gui.MenuItem({
			label: 'Checkout...',
			click: function() {
				BranchCheckoutDialog();
			}
		});
		this.items['branchCreate'] = new gui.MenuItem({
			label: 'Create...',
			click: function() {
				BranchCreateDialog();
			}
		});

		// Stash items
		this.items['stashSave'] = new gui.MenuItem({
			label: 'Save',
			click: function() {
				commander.stashSave(_handleGitResponse);
			}
		});
		this.items['stashPop'] = new gui.MenuItem({
			label: 'Pop',
			click: function() {
				commander.stashPop(_handleGitResponse);
			}
		});

		// Options items
		this.items['optionsZoomIn'] = new gui.MenuItem({
			label: 'Zoom In',
			click: function() {
				try {
					var zoom = gui.Window.get().zoomLevel;
					gui.Window.get().zoomLevel = [zoom + 1]; // FIXME: this should not be an array! #node-webkit
				} catch (e) {}
			}
		});
		this.items['optionsZoomOut'] = new gui.MenuItem({
			label: 'Zoom Out',
			click: function() {
				try {
					var zoom = gui.Window.get().zoomLevel;
					gui.Window.get().zoomLevel = [zoom - 1]; // FIXME: this should not be an array! #node-webkit
				} catch (e) {}
			}
		});
		this.items['optionsLessContext'] = new gui.MenuItem({
			label: 'Less context',
			click: function() {
				config.diff.contextLines--;
				if (config.diff.contextLines < 2) config.diff.contextLines = 2;
				updateGlobalStatus();
			}
		});
		this.items['optionsMoreContext'] = new gui.MenuItem({
			label: 'More context',
			click: function() {
				config.diff.contextLines++;
				updateGlobalStatus();
			}
		});
		this.items['optionsEolWhitespace'] = new gui.MenuItem({
			type: 'checkbox',
			checked: config.diff.ignoreEolWhitespace,
			label: 'Ignore EOL whitespace',
			click: function() {
				config.diff.ignoreEolWhitespace = this.checked;
				updateGlobalStatus();
			}
		});
		this.items['optionsSyntaxHighlighting'] = new gui.MenuItem({
			type: 'checkbox',
			checked: config.diff.highlight.enabled,
			label: 'Syntax highlighting',
			click: function() {
				config.diff.highlight.enabled = this.checked;
				updateGlobalStatus();
			}
		});

		// Help items
		this.items['helpReportBugs'] = new gui.MenuItem({
			label: 'Report bug...',
			click: function() {
				gui.Shell.openExternal('https://github.com/demian85/git-watcher/issues');
			}
		});

		this.menus.repository = new gui.Menu();
		this.menus.repository.append(this.items['repositoryOpen']);
		this.menus.repository.append(this.items['repositoryClose']);
		this.menus.repository.append(this.items['repositoryRefresh']);
		this.menus.repository.append(new gui.MenuItem({type: 'separator'}));
		this.menus.repository.append(this.items['repositorySubmoduleUpdate']);
		this.menus.repository.append(this.items['repositoryExplore']);
		this.menus.repository.append(this.items['repositoryBrowse']);
		this.menus.repository.append(new gui.MenuItem({type: 'separator'}));
		this.menus.repository.append(this.items['repositoryQuit']);

		this.menus.branch = new gui.Menu();
		this.menus.branch.append(this.items['branchCheckout']);
		this.menus.branch.append(this.items['branchCreate']);

		this.menus.stash = new gui.Menu();
		this.menus.stash.append(this.items['stashSave']);
		this.menus.stash.append(this.items['stashPop']);

		this.menus.options = new gui.Menu();
		this.menus.options.append(this.items['optionsZoomIn']);
		this.menus.options.append(this.items['optionsZoomOut']);
		this.menus.options.append(new gui.MenuItem({type: 'separator'}));
		this.menus.options.append(this.items['optionsLessContext']);
		this.menus.options.append(this.items['optionsMoreContext']);
		this.menus.options.append(this.items['optionsEolWhitespace']);
		this.menus.options.append(this.items['optionsSyntaxHighlighting']);

		this._createToolsMenu();

		this.menus.help = new gui.Menu();
		this.menus.help.append(this.items['helpReportBugs']);

		this.items['branchMenu'] = new gui.MenuItem({
			label: 'Branch',
			submenu: this.menus.branch
		});
		this.items['stashMenu'] = new gui.MenuItem({
			label: 'Stash',
			submenu: this.menus.stash
		});
		this.items['optionsMenu'] = new gui.MenuItem({
			label: 'Options',
			enabled: false,
			submenu: this.menus.options
		});
		this.items['toolsMenu'] = new gui.MenuItem({
			label: 'Tools',
			submenu: this.menus.tools
		});

		this.menubar = new gui.Menu({type: 'menubar'});
		this.menubar.append(new gui.MenuItem({
			label: 'Repository',
			submenu: this.menus.repository
		}));
		this.menubar.append(this.items['branchMenu']);
		this.menubar.append(this.items['stashMenu']);
		this.menubar.append(this.items['optionsMenu']);
		this.menubar.append(this.items['toolsMenu']);
		this.menubar.append(new gui.MenuItem({
			label: 'Help',
			submenu: this.menus.help
		}));

		gui.Window.get().menu = this.menubar;
	},

	_createToolsMenu: function() {
		this.menus.tools = new gui.Menu();
		config.tools.forEach(function(tool) {
			if (!tool.name || !tool.cmd) return;
			this.menus.tools.append(new gui.MenuItem({
				label: tool.name,
				click: function() {
					External.execTool(currentModulePath, tool);
				}
			}));
		}, this);
	},

	enableRepoMenu: function(enabled) {
		this.items['repositoryClose'].enabled = enabled;
		this.items['repositoryExplore'].enabled = enabled;
		this.items['repositoryBrowse'].enabled = enabled;
		this.items['repositoryRefresh'].enabled = enabled;
		this.items['repositorySubmoduleUpdate'].enabled = enabled;
		this.items['branchMenu'].enabled = enabled;
		this.items['stashMenu'].enabled = enabled;
		this.items['optionsMenu'].enabled = enabled;
		this.items['toolsMenu'].enabled = enabled;
	},

	showFileListMenu: function(file, type, x, y, line) {
		var menu = this._createFileMenu(file, type, line);
		menu.popup(x, y);
	}
};
<|endoftext|>


<commit_before>//default service locations
var proxyLocation = process.env.X509_USER_PROXY ? process.env.X509_USER_PROXY : 'user-proxy.pem';
var atmoLocation = process.env.ATMOSPHERE_URL ? process.env.ATMOSPHERE_URL : 'cloud.plgrid.pl';

//default ids of vms
var wfMainId = process.env.WF_MAIN_ID ? process.env.WF_MAIN_ID : 65;
var wfWorkerId = process.env.WF_WORDKER_ID ? process.env.WF_WORDKER_ID : 63;

var wfApplianceSetName = 'hfworkflow';

//default location of user stored config for hflowc
var configLocations = [
    '~/.hflow.config.json'
];

module.exports.default_config = {
    proxyLocation: proxyLocation,
    atmoLocation: atmoLocation,
    wfMainId: wfMainId,
    wfWorkerId: wfWorkerId,
    wfApplianceSetName: wfApplianceSetName
};

module.exports.configLocations = configLocations;
<commit_msg>Update image ids.<commit_after>//default service locations
var proxyLocation = process.env.X509_USER_PROXY ? process.env.X509_USER_PROXY : 'user-proxy.pem';
var atmoLocation = process.env.ATMOSPHERE_URL ? process.env.ATMOSPHERE_URL : 'cloud.plgrid.pl';

//default ids of vms
var wfMainId = process.env.WF_MAIN_ID ? process.env.WF_MAIN_ID : 66;
var wfWorkerId = process.env.WF_WORDKER_ID ? process.env.WF_WORDKER_ID : 63;

var wfApplianceSetName = 'hfworkflow';

//default location of user stored config for hflowc
var configLocations = [
    '~/.hflow.config.json'
];

module.exports.default_config = {
    proxyLocation: proxyLocation,
    atmoLocation: atmoLocation,
    wfMainId: wfMainId,
    wfWorkerId: wfWorkerId,
    wfApplianceSetName: wfApplianceSetName
};

module.exports.configLocations = configLocations;
<|endoftext|>