<commit_before>/**
 * @file This file is the main (i.e. body) javascript file of neural network web worker. It is not an importable module.
 *
 */

// In module (non-classic) web worker, static import is available. But at the same time, importScripts() will not be avbailable.
// For solving this problem, using classic (non-module) web worker so that tensorflow.js can be loaded by importScripts().
// At the same time, using dynamic import() to load ourselves module because import() can be used in classic (non-module) script.
//
//import * as Net from "./Net.js";

/**
 * The implementation of a neural network web worker.
 *
 */
class WorkerBody {

  /**
   *
   * @param {number} workerId
   *   A non-negative integer represents this worker's id. The id of the first worker should be 0.
   *
   * @param {Net.Config} neuralNetConfig
   *   The configuration of the neural network which will be created by this web worker.
   *
   * @param {string} weightsURL
   *   The URL of neural network weights. Every worker will load weights from the URL to initialize one neural network.
   */
  init( workerId = 0, tensorflowJsURL, neuralNetConfig, weightsURL ) {

    if ( workerId < 0 )
      workerId = 0;

    this.workerId = workerId;
    this.tensorflowJsURL = tensorflowJsURL;
    this.neuralNetConfig = neuralNetConfig;
    this.weightsURL = weightsURL;

    let bKeepInputTensor = false; // Because every web worker will copy the input, there is not necessary to keep input.

//!!! ...unfinished... global scope ? report progress ?

    // If a specific library module does not existed, all libraries might have not yet been loaded. (i.e. this is the first time WorkerBody.init() is called.)
    if ( !globalThis.NeuralNetProgress ) {

      globalThis.NeuralNetProgress = await import( "./NetProgress.js" ); // Load progress library in globalThis.ValueMax scope dynamically.

      this.initProgress = new NeuralNetProgress.InitProgress(); // The progress object could be created only after the progress library has been loaded.
      this.initProgress.libraryDownload.total = 3; // There are 3 libraries should be loaded: NetProgress, tensorflow.js, NeuralNet.
      this.initProgress.libraryDownload.accumulation = 0;

      ++this.initProgress.libraryDownload.accumulation; // The library NetProgress has been loaded.

//!!! ...unfinished... inform WorkerProxy progress changed.

      importScripts( tensorflowJsURL ); // Load tensorflow javascript library in global scope.
      ++this.initProgress.libraryDownload.accumulation; // The library tensorflow.js has been loaded.

      globalThis.NeuralNet = await import( "./Net.js" ); // Load neural network library in globalThis.NeuralNet scope dynamically.
      ++this.initProgress.libraryDownload.accumulation; // The library NeuralNet has been loaded.
    }

//!!! ...unfinished... the neuralNetConfig is still class NeuralNet.Config? Otherwise, the create_ScaledSourceTensor_from_ImageData_or_Canvas() will be lost.

    this.neuralNet = new NeuralNet.Base();
    this.neuralNet.init( neuralNetConfig, bKeepInputTensor );

//!!! ...unfinished...
    // Download and parse neural network weights. Also report downloading and parsing progress.

//       this.initProgress.weightsDownload;
//       this.initProgress.weightsParse;

  }

  disposeWorker() {
    if ( this.neuralNet ) {
      this.neuralNet.disposeTensors();
      this.neuralNet = null;
    }

//!!!??? calling close() when the next worker disposed ?
    close();
  }

  /**
   * Convert source image data to tensor3d, scale it, transfer scaled source typed-array back to WorkerProxy, compute neural network,
   * pass result back to WorkerProxy.
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {ImageData} sourceImageData
   *   The image data to be processed. The size should be [ sourceImageData.height, sourceImageData.width ]
   * = [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ] ]. And it should RGBA 4 channels.
   */
  imageData_transferBack_processTensor( processingId, sourceImageData ) {

    // Create (scaled) source image so that then neural network can process it.
    //
    // Usually, only the first web worker ( workerId == 0 ) is responsible for scaling the source image data to default size.
    // After that, all other web worker received the already scaled typed-array.
    //
    // The reason why not use source ImageData directly is that the next web worker could re-create tensor3d more effficiently.
    //
    // ImageData uses Uint8ClampedArray internally. If it is past directly, the next web worker needs create tensor3d by fromPixel()
    // which internally converts twice: from Uint8ClampedArray to Int32Array and from Int32Array to tensor3d.
    //
    // If passing typed-array (Float32Array), the next web worker could use it to re-create tensord3d directly.
    let scaledSourceTensor = this.neuralNetConfig.create_ScaledSourceTensor_from_ImageData_or_Canvas( sourceImageData );

//!!! ...unfinished... If the this.neuralNetConfig (which is past through web worker message) is not real NeuralNet.Config, the following should be used.
//    let scaledSourceTensor = NeuralNet.Config.create_ScaledSourceTensor_from_ImageData_or_Canvas.call( this.neuralNetConfig, sourceImageData );

    // Download the scaledSourceTensor as typed-array (asynchronously), and transfer it back to WorkerProxy (and inform WorkerController).
    //
    // The reason why it is done asynchronously is for not blocking the following computation of neural network.
    this.transferBackSourceScaledTensorAsync( processingId, scaledSourceTensor );

    // At the same time (the scaled source typed-array data is transferring back to WorkerProxy and then WorkerController), this worker is
    // still computing the neural network parallelly.
    this.processTensorAndDispose( processingId, scaledSourceTensor );
  }

  /**
   * Transfer scaled source typed-array data back to WorkerProxy, re-create source tensor, compute neural network, pass result back to WorkerProxy.
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-data to be processed. Its shape should be [ height, width, channel ] =
   * [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ].
   */
  typedArray_transferBack_processTensor( processingId, sourceTypedArray ) {
    let shape = [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ];

    // Re-create (scaled) source tensor.
    //
    // This should be done before calling transferBackSourceTypedArray() which will transfer (not copy) the sourceTypedArray and invalid it.
    let scaledSourceTensor = tf.tensor3d( sourceTypedArray, shape );

    // Transfer (not copy) the sourceTypedArray back to WorkerProxy). This will invalid sourceTypedArray.
    this.transferBackSourceTypedArray( processingId, sourceTypedArray );

    // At the same time (the sourceTypedArray is transferring back to WorkerProxy and then WorkerController), this worker is still computing
    // the neural network parallelly.
    this.processTensorAndDispose( processingId, scaledSourceTensor );
  }

  /**
   * Re-create source tensor, compute neural network, pass result back to WorkerProxy.
   *
   * It will not transfer source back to WorkerProxy. This is different from typedArray_TransferBack_processTensor().
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-data to be processed. Its shape should be [ height, width, channel ] =
   * [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ].
   */
  typedArray_processTensor( processingId, sourceTypedArray ) {
    let shape = [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ];
    let scaledSourceTensor = tf.tensor3d( sourceTypedArray, shape ); // Re-create (scaled) source tensor.
    this.processTensorAndDispose( processingId, scaledSourceTensor );
  }

  /**
   * Compute neural network, and pass result back to WorkerProxy.
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {tf.tensor3d} scaledSourceTensor
   *   The source tensor3d to be processed. The scaledSourceTensor.shape should be [ height, width, channel ] =
   * [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ].
   * This tensor will be disposed when processing is done.
   */
  processTensorAndDispose( processingId, scaledSourceTensor ) {

    // Note: scaledSourceTensor will be dispose because this.neuralNet is initialized with ( bKeepInputTensor == false ).
    let resultTensor3d = this.neuralNet.apply_and_destroy_or_keep( scaledSourceTensor, true );

    let resultTypedArray = resultTensor3d.dataSync(); // Download synchronously (because here is web worker).
    resultTensor3d.dispose(); // The result tensor should also be disposed.

    // Pass the output of neural network to WorkerProxy (and inform WorkerController).
    let message = { command: "processTensorResult", workerId: this.workerId, processingId: processingId, resultTypedArray: resultTypedArray };
    postMessage( message, [ message.resultTypedArray.buffer ] );
  }

  /**
   * Download the scaledSourceTensor as typed-array (Float32Array) asynchronously. Transfer the typed-array back to WorkerProxy
   * (and inform WorkerController) so that it can be past to next web worker.
   *
   * @param {number} processingId
   *   The id of this processing. It is used by WorkerProxy to found back corresponding promise.
   *
   * @param {tf.tensor3d} scaledSourceTensor
   *   The scaled source tensor3d generated by processImageData().
   */
  async transferBackSourceScaledTensorAsync( processingId, scaledSourceTensor ) {
    let sourceTypedArray = await scaledSourceTensor.data(); // Download scaled source tensor3d as typed-array (Float32Array) asynchronously.
    this.transferBackSourceTypedArray( processingId, sourceTypedArray );
  }

  /**
   * Transfer source typed-array back to WorkerProxy (and inform WorkerController) so that it can be past to next web worker.
   *
   * @param {number} processingId
   *   The id of this processing. It is used by WorkerProxy to found back corresponding promise.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-array past to processTypedArray(). This will become invalid after this call because it will be transferred (not
   * copied) back to WorkerProxy.
   */
  transferBackSourceTypedArray( processingId, sourceTypedArray ) {
    let message = { command: "transferBackSourceTypedArray", workerId: this.workerId, processingId: processingId, sourceTypedArray: sourceTypedArray };
    postMessage( message, [ message.sourceTypedArray.buffer ] );
  }

}

if ( globalThis.document ) {
  return; // In main document context (Not in worker context). Do nothing. (Should not happen)

// In worker context. Register message handler.

globalThis.onmessage = function( e ) {
  let message = e.data;

  switch ( message.command ) {
    case "init": //{ command: "init", workerId, tensorflowJsURL, neuralNetConfig, weightsURL };
      globalThis.workerBody = new WorkerBody();
      globalThis.workerBody.init( message.workerId, message.tensorflowJsURL, message.neuralNetConfig, message.weightsURL );
      break;

    case "disposeWorker": //{ command: "disposeWorker" };
      globalThis.workerBody.disposeWorker();
      break;

    case "imageData_transferBack_processTensor": //{ command: "imageData_transferBack_processTensor", processingId, sourceImageData };
      globalThis.workerBody.imageData_transferBack_processTensor( message.processingId, message.sourceImageData );
      break;

    case "typedArray_transferBack_processTensor": //{ command: "typedArray_transferBack_processTensor", processingId, sourceTypedArray };
      globalThis.workerBody.typedArray_transferBack_processTensor( message.processingId, message.sourceTypedArray );
      break;

    case "typedArray_processTensor": //{ command: "typedArray_processTensor", processingId, sourceTypedArray };
      globalThis.workerBody.typedArray_processTensor( message.processingId, message.sourceTypedArray );
      break;
  }
}
<commit_msg>Update WorkerBody.js<commit_after>/**
 * @file This file is the main (i.e. body) javascript file of neural network web worker. It is not an importable module.
 *
 */

// In module (non-classic) web worker, static import is available. But at the same time, importScripts() will not be avbailable.
// For solving this problem, using classic (non-module) web worker so that tensorflow.js can be loaded by importScripts().
// At the same time, using dynamic import() to load ourselves module because import() can be used in classic (non-module) script.
//
//import * as Net from "./Net.js";

/**
 * The implementation of a neural network web worker.
 *
 */
class WorkerBody {

  /**
   *
   * @param {number} workerId
   *   A non-negative integer represents this worker's id. The id of the first worker should be 0.
   *
   * @param {Net.Config} neuralNetConfig
   *   The configuration of the neural network which will be created by this web worker.
   *
   * @param {string} weightsURL
   *   The URL of neural network weights. Every worker will load weights from the URL to initialize one neural network.
   */
  init( workerId = 0, tensorflowJsURL, neuralNetConfig, weightsURL ) {

    if ( workerId < 0 )
      workerId = 0;

    this.workerId = workerId;
    this.tensorflowJsURL = tensorflowJsURL;
    this.neuralNetConfig = neuralNetConfig;
    this.weightsURL = weightsURL;

    let bKeepInputTensor = false; // Because every web worker will copy the input, there is not necessary to keep input.

//!!! ...unfinished... global scope ? report progress ?

    // If a specific library module does not existed, all libraries might have not yet been loaded. (i.e. this is the first time WorkerBody.init() is called.)
    if ( !globalThis.NeuralNetProgress ) {

      globalThis.NeuralNetProgress = await import( "./NetProgress.js" ); // Load progress library in globalThis.ValueMax scope dynamically.

      this.initProgress = new NeuralNetProgress.InitProgress(); // The progress object could be created only after the progress library has been loaded.
      this.initProgress.libraryDownload.total = 3; // There are 3 libraries should be loaded: NetProgress, tensorflow.js, NeuralNet.
      this.initProgress.libraryDownload.accumulation = 0;

      ++this.initProgress.libraryDownload.accumulation; // The library NetProgress has been loaded.

//!!! ...unfinished... inform WorkerProxy progress changed.

      importScripts( tensorflowJsURL ); // Load tensorflow javascript library in global scope.
      ++this.initProgress.libraryDownload.accumulation; // The library tensorflow.js has been loaded.

      globalThis.NeuralNet = await import( "./Net.js" ); // Load neural network library in globalThis.NeuralNet scope dynamically.
      ++this.initProgress.libraryDownload.accumulation; // The library NeuralNet has been loaded.
    }

//!!! ...unfinished... the neuralNetConfig is still class NeuralNet.Config? Otherwise, the create_ScaledSourceTensor_from_ImageData_or_Canvas() will be lost.

    this.neuralNet = new NeuralNet.Base();
    this.neuralNet.init( neuralNetConfig, bKeepInputTensor );

//!!! ...unfinished...
    // Download and parse neural network weights. Also report downloading and parsing progress.

//       this.initProgress.weightsDownload;
//       this.initProgress.weightsParse;

  }

  disposeWorker() {
    if ( this.neuralNet ) {
      this.neuralNet.disposeTensors();
      this.neuralNet = null;
    }

//!!!??? calling close() when the next worker disposed ?
    close();
  }

  /**
   * Convert source image data to tensor3d, scale it, transfer scaled source typed-array back to WorkerProxy, compute neural network,
   * pass result back to WorkerProxy.
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {ImageData} sourceImageData
   *   The image data to be processed. The size should be [ sourceImageData.height, sourceImageData.width ]
   * = [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ] ]. And it should RGBA 4 channels.
   */
  imageData_transferBack_processTensor( processingId, sourceImageData ) {

    // Create (scaled) source image so that then neural network can process it.
    //
    // Usually, only the first web worker ( workerId == 0 ) is responsible for scaling the source image data to default size.
    // After that, all other web worker received the already scaled typed-array.
    //
    // The reason why not use source ImageData directly is that the next web worker could re-create tensor3d more effficiently.
    //
    // ImageData uses Uint8ClampedArray internally. If it is past directly, the next web worker needs create tensor3d by fromPixel()
    // which internally converts twice: from Uint8ClampedArray to Int32Array and from Int32Array to tensor3d.
    //
    // If passing typed-array (Float32Array), the next web worker could use it to re-create tensord3d directly.
    let scaledSourceTensor = this.neuralNetConfig.create_ScaledSourceTensor_from_ImageData_or_Canvas( sourceImageData );

//!!! ...unfinished... If the this.neuralNetConfig (which is past through web worker message) is not real NeuralNet.Config, the following should be used.
//    let scaledSourceTensor = NeuralNet.Config.create_ScaledSourceTensor_from_ImageData_or_Canvas.call( this.neuralNetConfig, sourceImageData );

    // Download the scaledSourceTensor as typed-array (asynchronously), and transfer it back to WorkerProxy (and inform WorkerController).
    //
    // The reason why it is done asynchronously is for not blocking the following computation of neural network.
    this.transferBackSourceScaledTensorAsync( processingId, scaledSourceTensor );

    // At the same time (the scaled source typed-array data is transferring back to WorkerProxy and then WorkerController), this worker is
    // still computing the neural network parallelly.
    this.processTensorAndDispose( processingId, scaledSourceTensor );
  }

  /**
   * Transfer scaled source typed-array data back to WorkerProxy, re-create source tensor, compute neural network, pass result back to WorkerProxy.
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-data to be processed. Its shape should be [ height, width, channel ] =
   * [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ].
   */
  typedArray_transferBack_processTensor( processingId, sourceTypedArray ) {
    let shape = [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ];

    // Re-create (scaled) source tensor.
    //
    // This should be done before calling transferBackSourceTypedArray() which will transfer (not copy) the sourceTypedArray and invalid it.
    let scaledSourceTensor = tf.tensor3d( sourceTypedArray, shape );

    // Transfer (not copy) the sourceTypedArray back to WorkerProxy). This will invalid sourceTypedArray.
    this.transferBackSourceTypedArray( processingId, sourceTypedArray );

    // At the same time (the sourceTypedArray is transferring back to WorkerProxy and then WorkerController), this worker is still computing
    // the neural network parallelly.
    this.processTensorAndDispose( processingId, scaledSourceTensor );
  }

  /**
   * Re-create source tensor, compute neural network, pass result back to WorkerProxy.
   *
   * It will not transfer source back to WorkerProxy. This is different from typedArray_TransferBack_processTensor().
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-data to be processed. Its shape should be [ height, width, channel ] =
   * [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ].
   */
  typedArray_processTensor( processingId, sourceTypedArray ) {
    let shape = [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ];
    let scaledSourceTensor = tf.tensor3d( sourceTypedArray, shape ); // Re-create (scaled) source tensor.
    this.processTensorAndDispose( processingId, scaledSourceTensor );
  }

  /**
   * Compute neural network, and pass result back to WorkerProxy.
   *
   * @param {number} processingId
   *   The id of this processing. It is used when reporting processing resultso that WorkerProxy could find back corresponding promise.
   *
   * @param {tf.tensor3d} scaledSourceTensor
   *   The source tensor3d to be processed. The scaledSourceTensor.shape should be [ height, width, channel ] =
   * [ this.neuralNet.sourceImageHeightWidth[ 0 ], this.neuralNet.sourceImageHeightWidth[ 1 ], this.neuralNet.config.sourceChannelCount ].
   * This tensor will be disposed when processing is done.
   */
  processTensorAndDispose( processingId, scaledSourceTensor ) {

    // Note: scaledSourceTensor will be dispose because this.neuralNet is initialized with ( bKeepInputTensor == false ).
    let resultTensor3d = this.neuralNet.apply_and_destroy_or_keep( scaledSourceTensor, true );

    let resultTypedArray = resultTensor3d.dataSync(); // Download synchronously (because here is web worker).
    resultTensor3d.dispose(); // The result tensor should also be disposed.

    // Pass the output of neural network to WorkerProxy (and inform WorkerController).
    let message = { command: "processTensorResult", workerId: this.workerId, processingId: processingId, resultTypedArray: resultTypedArray };
    postMessage( message, [ message.resultTypedArray.buffer ] );
  }

  /**
   * Download the scaledSourceTensor as typed-array (Float32Array) asynchronously. Transfer the typed-array back to WorkerProxy
   * (and inform WorkerController) so that it can be past to next web worker.
   *
   * @param {number} processingId
   *   The id of this processing. It is used by WorkerProxy to found back corresponding promise.
   *
   * @param {tf.tensor3d} scaledSourceTensor
   *   The scaled source tensor3d generated by processImageData().
   */
  async transferBackSourceScaledTensorAsync( processingId, scaledSourceTensor ) {
    let sourceTypedArray = await scaledSourceTensor.data(); // Download scaled source tensor3d as typed-array (Float32Array) asynchronously.
    this.transferBackSourceTypedArray( processingId, sourceTypedArray );
  }

  /**
   * Transfer source typed-array back to WorkerProxy (and inform WorkerController) so that it can be past to next web worker.
   *
   * @param {number} processingId
   *   The id of this processing. It is used by WorkerProxy to found back corresponding promise.
   *
   * @param {Float32Array} sourceTypedArray
   *   The source typed-array past to processTypedArray(). This will become invalid after this call because it will be transferred (not
   * copied) back to WorkerProxy.
   */
  transferBackSourceTypedArray( processingId, sourceTypedArray ) {
    let message = { command: "transferBackSourceTypedArray", workerId: this.workerId, processingId: processingId, sourceTypedArray: sourceTypedArray };
    postMessage( message, [ message.sourceTypedArray.buffer ] );
  }

//!!! ...unfinished... How to know and set total of the following:
//       this.initProgress.libraryDownload;
//       this.initProgress.weightsDownload;
//       this.initProgress.weightsParse;

  /** Inform WorkerProxy to reset initProgress. */
  initProgress_restAccumulation_postMessage() {
    let message = { command: "initProgress_restAccumulation", workerId: this.workerId };
    postMessage( message );
  }

  /** Inform WorkerProxy to advance initProgress.libraryDownload. */
  initProgress_libraryDownload_postMessage() {
//    let message = { command: "initProgress_libraryDownload", workerId: this.workerId, total: this.initProgress.libraryDownload.total, accumulation: this.initProgress.libraryDownload.accumulation };
//    let message = { command: "initProgress_libraryDownload", workerId: this.workerId, initProgress: this.initProgress.libraryDownload };
    let message = { command: "initProgress_libraryDownload", workerId: this.workerId, total: this.initProgress.libraryDownload.total, accumulation: this.initProgress.libraryDownload.accumulation };
    postMessage( message );
  }
}

if ( globalThis.document ) {
  return; // In main document context (Not in worker context). Do nothing. (Should not happen)

// In worker context. Register message handler.

globalThis.onmessage = function( e ) {
  let message = e.data;

  switch ( message.command ) {
    case "init": //{ command: "init", workerId, tensorflowJsURL, neuralNetConfig, weightsURL };
      globalThis.workerBody = new WorkerBody();
      globalThis.workerBody.init( message.workerId, message.tensorflowJsURL, message.neuralNetConfig, message.weightsURL );
      break;

    case "disposeWorker": //{ command: "disposeWorker" };
      globalThis.workerBody.disposeWorker();
      break;

    case "imageData_transferBack_processTensor": //{ command: "imageData_transferBack_processTensor", processingId, sourceImageData };
      globalThis.workerBody.imageData_transferBack_processTensor( message.processingId, message.sourceImageData );
      break;

    case "typedArray_transferBack_processTensor": //{ command: "typedArray_transferBack_processTensor", processingId, sourceTypedArray };
      globalThis.workerBody.typedArray_transferBack_processTensor( message.processingId, message.sourceTypedArray );
      break;

    case "typedArray_processTensor": //{ command: "typedArray_processTensor", processingId, sourceTypedArray };
      globalThis.workerBody.typedArray_processTensor( message.processingId, message.sourceTypedArray );
      break;
  }
}
<|endoftext|>


<commit_before>const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Lang = imports.lang;
const Main = imports.ui.main;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const St = imports.gi.St;
const Util = imports.misc.util;
const versionCheck = imports.misc.extensionUtils.versionCheck;

const DeskChangerSettings = Me.imports.settings.DeskChangerSettings;
const DeskChangerVersion = Me.metadata.version;
const GnomeShellVersion = Main.shellDBusService.ShellVersion;

const DeskChangerBaseMenuItem = new Lang.Class({
	Name: 'DeskChangerBaseMenuItem',
	Extends: PopupMenu.PopupBaseMenuItem,

	_addActor: function (widget, params)
	{
		if (versionCheck(['3.10'], GnomeShellVersion)) {
			this.actor.add(widget, params);
		} else {
			this.actorAdd(widget, params);
		}
	}
});

const DeskChangerButton = new Lang.Class({
	Name: 'DeskChangerButton',
	Extends: St.Button,

	_init: function(icon, callback)
	{
		this.icon = new St.Icon({icon_name: icon+'-symbolic', icon_size: 20});
		this.parent({
			child: this.icon,
			style_class: (versionCheck(['3.10'], GnomeShellVersion))? 'system-menu-action' : 'notification-icon-button control-button'
		});
		this.connect('clicked', callback);
	},

	set_icon: function(icon)
	{
		this.icon.icon_name = icon+'-symbolic';
	}
});

const DeskChangerControls = new Lang.Class({
	Name: 'DeskChangerControls',
	Extends: DeskChangerBaseMenuItem,

	_init: function (dbus, settings)
	{
		this._dbus = dbus;
		this._settings = settings;
		this.parent({can_focus: false, reactive: false});

		this._next = new DeskChangerButton('media-skip-forward', Lang.bind(this._dbus, function () {
			this.nextSync(true);
		}));
		this._prev = new DeskChangerButton('media-skip-backward', Lang.bind(this._dbus, function () {
			this.prevSync();
		}));
		this._random = new DeskChangerStateButton([
			{
				icon: 'media-playlist-shuffle',
				name: 'random'
			},
			{
				icon: 'media-playlist-repeat',
				name: 'ordered'
			}
		], Lang.bind(this, this._toggle_random));
		this._random.set_state((this._settings.random)? 'random' : 'ordered');
		this._timer = new DeskChangerStateButton([
			{
				icon: 'media-playback-stop',
				name: 'enable'
			},
			{
				icon: 'media-playback-start',
				name: 'disable'
			}
		], Lang.bind(this, this._toggle_timer));
		this._timer.set_state((this._settings.timer_enabled)? 'enable' : 'disable');

		this._addActor(this._prev, {expand: true, x_fill: false});
		this._addActor(this._random, {expand: true, x_fill: false});
		this._addActor(this._timer, {expand: true, x_fill: false});
		this._addActor(this._next, {expand: true, x_fill: false});
	},

	_toggle_random: function (state)
	{
		debug('setting order to '+state);
		this._settings.random = (state == 'random');
	},

	_toggle_timer: function (state)
	{
		debug(state+'ing timer');
		this._settings.timer_enabled = (state == 'enable');
	}
});

const DeskChangerDaemonControls = new Lang.Class({
	Name: 'DeskChangerDaemonControls',
	Extends: PopupMenu.PopupSwitchMenuItem,

	_init: function ()
	{
		this._path = Me.dir.get_path();
		this.parent('DeskChanger Daemon');
		debug(this.is_running());
		this.setToggleState(this.is_running());
		this.connect('toggled', Lang.bind(this, this._toggle_daemon));
	},

	is_running: function ()
	{
		// I need a better way to test this..
		return(GLib.file_test(this._path+'/daemon.pid', GLib.FileTest.EXISTS));
	},

	_toggle_daemon: function ()
	{
		var out;

		if (this.is_running()) {
			GLib.spawn_command_line_sync(this._path+'/daemon.py stop', out);
		} else {
			GLib.spawn_command_line_sync(this._path+'/daemon.py start', out);
		}

		debug(out);
	}
});

const DeskChangerDBusInterface = <interface name="org.gnome.shell.extensions.desk_changer">
	<method name="next">
		<arg direction="in" name="history" type="b" />
	</method>
	<method name="prev">
	</method>
	<method name="up_next">
		<arg direction="out" name="next_file" type="s" />
	</method>
	<signal name="next_file">
		<arg direction="out" name="file" type="s" />
	</signal>
</interface>;

const DeskChangerDBusProxy = Gio.DBusProxy.makeProxyWrapper(DeskChangerDBusInterface);

const DeskChangerIcon = new Lang.Class({
	Name: 'DeskChangerIcon',
	Extends: St.BoxLayout,

	_init: function()
	{
		this.parent({style_class: 'panel-status-menu-box'});
		this.add_child(new St.Icon({
			icon_name: 'emblem-photos-symbolic',
			style_class: 'system-status-icon'
		}));
	}
});

/**
 * This is the actual indicator that should be added to the main panel.
 *
 * @type {Lang.Class}
 */
const DeskChangerIndicator = new Lang.Class({
	Name: 'DeskChangerIndicator',
	Extends: PanelMenu.Button,

	_init: function ()
	{
		this.settings = new DeskChangerSettings();
		this.parent(0.0, 'DeskChanger');
		this._dbus = new DeskChangerDBusProxy(Gio.DBus.session, 'org.gnome.shell.extensions.desk_changer', '/org/gnome/shell/extensions/desk_changer');
		this.actor.add_child(new DeskChangerIcon());
		this.menu.addMenuItem(new DeskChangerSwitch('Change with Profile', 'auto_rotate', this.settings));
		this.menu.addMenuItem(new DeskChangerSwitch('Notifications', 'notifications', this.settings));
		this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
		this.menu.addMenuItem(new DeskChangerPreview(this._dbus));
		this.menu.addMenuItem(new DeskChangerOpenCurrent());
		this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
		this.menu.addMenuItem(new DeskChangerControls(this._dbus, this.settings));
		this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
		this.menu.addMenuItem(new DeskChangerDaemonControls());
	},

	destroy: function ()
	{
		this.parent();
	}
});

const DeskChangerOpenCurrent = new Lang.Class({
	Name: 'DeskChangerOpenCurrent',
	Extends: PopupMenu.PopupMenuItem,

	_init: function ()
	{
		this._background = new Gio.Settings({'schema': 'org.gnome.desktop.background'});
		this.parent('Open Current Wallpaper');
		this._activate_id = this.connect('activate', Lang.bind(this, this._activate));
	},

	destroy: function ()
	{
		this.disconnect(this._activate_id);
		this.parent();
	},

	_activate: function()
	{
		debug('opening current wallpaper '+this._background.get_string('picture-uri'));
		Util.spawn(['xdg-open', this._background.get_string('picture-uri')]);
	}
});

const DeskChangerPreview = new Lang.Class({
	Name: 'DeskChangerPreview',
	Extends: PopupMenu.PopupBaseMenuItem,

	_init: function (_dbus)
	{
		this._file = null;
		this._dbus = _dbus;
		this.parent({reactive: true});
		this._box = new St.BoxLayout({vertical: true});
		if (versionCheck(['3.10'], GnomeShellVersion)) {
			this.actor.add(this._box, {align: St.Align.MIDDLE, span: -1});
		} else {
			this.actorAdd(this._box, {align: St.Align.MIDDLE, span: -1});
		}
		this._label = new St.Label({text: "Next Wallpaper\n"});
		this._box.add(this._label);
		this._wallpaper = new St.Bin({});
		this._box.add(this._wallpaper);
		this._texture = new Clutter.Texture({
			filter_quality: Clutter.TextureQuality.HIGH,
			keep_aspect_ratio: true,
			width: 220
		});
		this._wallpaper.set_child(this._texture);
		this._next_file_id = this._dbus.connectSignal('next_file', Lang.bind(this, function (emitter, signalName, parameters) {
			var file = parameters[0];
			this.set_wallpaper(file);
		}));
		this._dbus.up_nextRemote(Lang.bind(this, function (result, e) {
			if (result)
				this.set_wallpaper(result[0]);
		}));
		this._activate_id = this.connect('activate', Lang.bind(this, this._clicked));
	},

	destroy: function ()
	{
		if (this._next_file_id) {
			debug('removing dbus next_file handler '+this._next_file_id);
			this._dbus.disconnectSignal(this._next_file_id);
		}

		if (this._activate_id) {
			this.disconnect(this._activate_id);
		}

		this._wallpaper.destroy();
		this._texture.destroy();
		this._label.destroy();
		this._box.destroy();
		this.parent();
	},

	set_wallpaper: function (file)
	{
		this._file = file;
		file = file.replace('file://', '');
		debug('setting preview to '+file);
		if (this._texture.set_from_file(file) === false) {
			debug('ERROR: Failed to set preview of ' + file);
		}
	},

	_clicked: function ()
	{
		if (this._file) {
			debug('opening file '+this._file);
			Util.spawn(['xdg-open', this._file]);
		} else {
			debug('ERROR: no preview currently set');
		}
	}
});

const DeskChangerStateButton = new Lang.Class({
	Name: 'DeskChangerStateButton',
	Extends: DeskChangerButton,

	_init: function(states, callback)
	{
		if (states.length < 2) {
			RangeError('You must provide at least two states for the button');
		}

		this._callback = callback;
		this._states = states;
		this._state = 0;
		this.parent(this._states[0].icon, Lang.bind(this, this._clicked));
	},

	set_state: function(state)
	{
		if (state == this._states[this._state].name) {
			// We are alread on that state... dafuq?!
			return;
		}

		for (var i = 0; i < this._states.length; i++) {
			if (this._states[i].name == state) {
				this.set_icon(this._states[i].icon);
				this._state = i;
				break;
			}
		}
	},

	_clicked: function()
	{
		var state = this._state;
		if (++state >= this._states.length)
			state = 0;
		state = this._states[state].name;
		this.set_state(state);
		this._callback(state);
	}
});

const DeskChangerSwitch = new Lang.Class({
	Name: 'DeskChangerSwitch',
	Extends: PopupMenu.PopupSwitchMenuItem,

	_init: function (label, setting, settings)
	{
		this._setting = setting;
		this._settings = settings;
		this.parent(label);
		this.setToggleState(settings[setting]);
		this._handler_changed = settings.connect('changed::'+setting, Lang.bind(this, this._changed));
		this._handler_toggled = this.connect('toggled', Lang.bind(this, this._toggled));
	},

	destroy: function ()
	{
		if (this._handler_changed) {
			this._settings.disconnect(this._handler);
		}

		if (this._handler_toggled) {
			this.disconnect(this._handler_toggled);
		}
	},

	_changed: function (settings, key)
	{
		this.setToggledState(settings[key]);
	},

	_toggled: function ()
	{
		debug('setting '+this._setting+' to '+this.state);
		this._settings[this._setting] = this.state;
	}
});

function debug(output)
{
	var date = new Date();
	output = '['+date.toLocaleString()+']'+Me.metadata.uuid+': '+output;
	log(output);
}

function disable()
{
	debug('disabling extension');
	if (Main.panel.statusArea.deskchanger) {
		Main.panel.statusArea.deskchanger.destroy();
	}
}

function enable()
{
	debug('enabling extension');
	Main.panel.addToStatusArea('deskchanger', new DeskChangerIndicator());
}

function init()
{
	debug('initalizing extension version: '+DeskChangerVersion);
}<commit_msg>changed is_running function to be a little more productive... still needs a lot of work and doesn't work right yet... :(<commit_after>const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Lang = imports.lang;
const Main = imports.ui.main;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const St = imports.gi.St;
const Util = imports.misc.util;
const versionCheck = imports.misc.extensionUtils.versionCheck;

const DeskChangerSettings = Me.imports.settings.DeskChangerSettings;
const DeskChangerVersion = Me.metadata.version;
const GnomeShellVersion = Main.shellDBusService.ShellVersion;

const DeskChangerBaseMenuItem = new Lang.Class({
	Name: 'DeskChangerBaseMenuItem',
	Extends: PopupMenu.PopupBaseMenuItem,

	_addActor: function (widget, params)
	{
		if (versionCheck(['3.10'], GnomeShellVersion)) {
			this.actor.add(widget, params);
		} else {
			this.actorAdd(widget, params);
		}
	}
});

const DeskChangerButton = new Lang.Class({
	Name: 'DeskChangerButton',
	Extends: St.Button,

	_init: function(icon, callback)
	{
		this.icon = new St.Icon({icon_name: icon+'-symbolic', icon_size: 20});
		this.parent({
			child: this.icon,
			style_class: (versionCheck(['3.10'], GnomeShellVersion))? 'system-menu-action' : 'notification-icon-button control-button'
		});
		this.connect('clicked', callback);
	},

	set_icon: function(icon)
	{
		this.icon.icon_name = icon+'-symbolic';
	}
});

const DeskChangerControls = new Lang.Class({
	Name: 'DeskChangerControls',
	Extends: DeskChangerBaseMenuItem,

	_init: function (dbus, settings)
	{
		this._dbus = dbus;
		this._settings = settings;
		this.parent({can_focus: false, reactive: false});

		this._next = new DeskChangerButton('media-skip-forward', Lang.bind(this._dbus, function () {
			this.nextSync(true);
		}));
		this._prev = new DeskChangerButton('media-skip-backward', Lang.bind(this._dbus, function () {
			this.prevSync();
		}));
		this._random = new DeskChangerStateButton([
			{
				icon: 'media-playlist-shuffle',
				name: 'random'
			},
			{
				icon: 'media-playlist-repeat',
				name: 'ordered'
			}
		], Lang.bind(this, this._toggle_random));
		this._random.set_state((this._settings.random)? 'random' : 'ordered');
		this._timer = new DeskChangerStateButton([
			{
				icon: 'media-playback-stop',
				name: 'enable'
			},
			{
				icon: 'media-playback-start',
				name: 'disable'
			}
		], Lang.bind(this, this._toggle_timer));
		this._timer.set_state((this._settings.timer_enabled)? 'enable' : 'disable');

		this._addActor(this._prev, {expand: true, x_fill: false});
		this._addActor(this._random, {expand: true, x_fill: false});
		this._addActor(this._timer, {expand: true, x_fill: false});
		this._addActor(this._next, {expand: true, x_fill: false});
	},

	_toggle_random: function (state)
	{
		debug('setting order to '+state);
		this._settings.random = (state == 'random');
	},

	_toggle_timer: function (state)
	{
		debug(state+'ing timer');
		this._settings.timer_enabled = (state == 'enable');
	}
});

const DeskChangerDaemonControls = new Lang.Class({
	Name: 'DeskChangerDaemonControls',
	Extends: PopupMenu.PopupSwitchMenuItem,

	_init: function ()
	{
		this._is_running = false;
		this._path = Me.dir.get_path();
		this.parent('DeskChanger Daemon');
		this.setToggleState(this.is_running());
		this.connect('toggled', Lang.bind(this, this._toggle_daemon));
	},

	is_running: function ()
	{
		if (this._pid == undefined && GLib.file_test(this._path+'/daemon.pid', GLib.FileTest.EXISTS)) {
			var out;
			out =  GLib.file_get_contents(this._path+'/daemon.pid');
			if (out[0] == true) {
				this._pid = out[1];
				debug('got pid ' + this._pid + ' for daemon');
			} else {
				debug('failed to open pid file, daemon not running?');
			}
		}

		if (this._pid != undefined && this._child_watch == undefined) {
			this._child_watch = GLib.child_watch_add(GLib.PRIORITY_DEFAULT, this._pid, Lang.bind(this, function (pid, status, data) {
				debug(pid + ' ' + status + ' ' + data);
				return(0);
			}), {}, null);
			debug('added child watch ' + this._child_watch);
		}

		return(this._is_running);
	},

	_toggle_daemon: function ()
	{
		var out;

		if (this.is_running()) {
			GLib.spawn_command_line_sync(this._path+'/daemon.py stop', out);
		} else {
			GLib.spawn_command_line_sync(this._path+'/daemon.py start', out);
		}

		debug(out);
	}
});

const DeskChangerDBusInterface = <interface name="org.gnome.shell.extensions.desk_changer">
	<method name="next">
		<arg direction="in" name="history" type="b" />
	</method>
	<method name="prev">
	</method>
	<method name="up_next">
		<arg direction="out" name="next_file" type="s" />
	</method>
	<signal name="next_file">
		<arg direction="out" name="file" type="s" />
	</signal>
</interface>;

const DeskChangerDBusProxy = Gio.DBusProxy.makeProxyWrapper(DeskChangerDBusInterface);

const DeskChangerIcon = new Lang.Class({
	Name: 'DeskChangerIcon',
	Extends: St.BoxLayout,

	_init: function()
	{
		this.parent({style_class: 'panel-status-menu-box'});
		this.add_child(new St.Icon({
			icon_name: 'emblem-photos-symbolic',
			style_class: 'system-status-icon'
		}));
	}
});

/**
 * This is the actual indicator that should be added to the main panel.
 *
 * @type {Lang.Class}
 */
const DeskChangerIndicator = new Lang.Class({
	Name: 'DeskChangerIndicator',
	Extends: PanelMenu.Button,

	_init: function ()
	{
		this.settings = new DeskChangerSettings();
		this.parent(0.0, 'DeskChanger');
		this._dbus = new DeskChangerDBusProxy(Gio.DBus.session, 'org.gnome.shell.extensions.desk_changer', '/org/gnome/shell/extensions/desk_changer');
		this.actor.add_child(new DeskChangerIcon());
		this.menu.addMenuItem(new DeskChangerSwitch('Change with Profile', 'auto_rotate', this.settings));
		this.menu.addMenuItem(new DeskChangerSwitch('Notifications', 'notifications', this.settings));
		this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
		this.menu.addMenuItem(new DeskChangerPreview(this._dbus));
		this.menu.addMenuItem(new DeskChangerOpenCurrent());
		this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
		this.menu.addMenuItem(new DeskChangerControls(this._dbus, this.settings));
		this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
		this.menu.addMenuItem(new DeskChangerDaemonControls());
	},

	destroy: function ()
	{
		this.parent();
	}
});

const DeskChangerOpenCurrent = new Lang.Class({
	Name: 'DeskChangerOpenCurrent',
	Extends: PopupMenu.PopupMenuItem,

	_init: function ()
	{
		this._background = new Gio.Settings({'schema': 'org.gnome.desktop.background'});
		this.parent('Open Current Wallpaper');
		this._activate_id = this.connect('activate', Lang.bind(this, this._activate));
	},

	destroy: function ()
	{
		this.disconnect(this._activate_id);
		this.parent();
	},

	_activate: function()
	{
		debug('opening current wallpaper '+this._background.get_string('picture-uri'));
		Util.spawn(['xdg-open', this._background.get_string('picture-uri')]);
	}
});

const DeskChangerPreview = new Lang.Class({
	Name: 'DeskChangerPreview',
	Extends: PopupMenu.PopupBaseMenuItem,

	_init: function (_dbus)
	{
		this._file = null;
		this._dbus = _dbus;
		this.parent({reactive: true});
		this._box = new St.BoxLayout({vertical: true});
		if (versionCheck(['3.10'], GnomeShellVersion)) {
			this.actor.add(this._box, {align: St.Align.MIDDLE, span: -1});
		} else {
			this.actorAdd(this._box, {align: St.Align.MIDDLE, span: -1});
		}
		this._label = new St.Label({text: "Next Wallpaper\n"});
		this._box.add(this._label);
		this._wallpaper = new St.Bin({});
		this._box.add(this._wallpaper);
		this._texture = new Clutter.Texture({
			filter_quality: Clutter.TextureQuality.HIGH,
			keep_aspect_ratio: true,
			width: 220
		});
		this._wallpaper.set_child(this._texture);
		this._next_file_id = this._dbus.connectSignal('next_file', Lang.bind(this, function (emitter, signalName, parameters) {
			var file = parameters[0];
			this.set_wallpaper(file);
		}));
		this._dbus.up_nextRemote(Lang.bind(this, function (result, e) {
			if (result)
				this.set_wallpaper(result[0]);
		}));
		this._activate_id = this.connect('activate', Lang.bind(this, this._clicked));
	},

	destroy: function ()
	{
		if (this._next_file_id) {
			debug('removing dbus next_file handler '+this._next_file_id);
			this._dbus.disconnectSignal(this._next_file_id);
		}

		if (this._activate_id) {
			this.disconnect(this._activate_id);
		}

		this._wallpaper.destroy();
		this._texture.destroy();
		this._label.destroy();
		this._box.destroy();
		this.parent();
	},

	set_wallpaper: function (file)
	{
		this._file = file;
		file = file.replace('file://', '');
		debug('setting preview to '+file);
		if (this._texture.set_from_file(file) === false) {
			debug('ERROR: Failed to set preview of ' + file);
		}
	},

	_clicked: function ()
	{
		if (this._file) {
			debug('opening file '+this._file);
			Util.spawn(['xdg-open', this._file]);
		} else {
			debug('ERROR: no preview currently set');
		}
	}
});

const DeskChangerStateButton = new Lang.Class({
	Name: 'DeskChangerStateButton',
	Extends: DeskChangerButton,

	_init: function(states, callback)
	{
		if (states.length < 2) {
			RangeError('You must provide at least two states for the button');
		}

		this._callback = callback;
		this._states = states;
		this._state = 0;
		this.parent(this._states[0].icon, Lang.bind(this, this._clicked));
	},

	set_state: function(state)
	{
		if (state == this._states[this._state].name) {
			// We are alread on that state... dafuq?!
			return;
		}

		for (var i = 0; i < this._states.length; i++) {
			if (this._states[i].name == state) {
				this.set_icon(this._states[i].icon);
				this._state = i;
				break;
			}
		}
	},

	_clicked: function()
	{
		var state = this._state;
		if (++state >= this._states.length)
			state = 0;
		state = this._states[state].name;
		this.set_state(state);
		this._callback(state);
	}
});

const DeskChangerSwitch = new Lang.Class({
	Name: 'DeskChangerSwitch',
	Extends: PopupMenu.PopupSwitchMenuItem,

	_init: function (label, setting, settings)
	{
		this._setting = setting;
		this._settings = settings;
		this.parent(label);
		this.setToggleState(settings[setting]);
		this._handler_changed = settings.connect('changed::'+setting, Lang.bind(this, this._changed));
		this._handler_toggled = this.connect('toggled', Lang.bind(this, this._toggled));
	},

	destroy: function ()
	{
		if (this._handler_changed) {
			this._settings.disconnect(this._handler);
		}

		if (this._handler_toggled) {
			this.disconnect(this._handler_toggled);
		}
	},

	_changed: function (settings, key)
	{
		this.setToggledState(settings[key]);
	},

	_toggled: function ()
	{
		debug('setting '+this._setting+' to '+this.state);
		this._settings[this._setting] = this.state;
	}
});

function debug(output)
{
	var date = new Date();
	output = '['+date.toLocaleString()+']'+Me.metadata.uuid+': '+output;
	log(output);
}

function disable()
{
	debug('disabling extension');
	if (Main.panel.statusArea.deskchanger) {
		Main.panel.statusArea.deskchanger.destroy();
	}
}

function enable()
{
	debug('enabling extension');
	Main.panel.addToStatusArea('deskchanger', new DeskChangerIndicator());
}

function init()
{
	debug('initalizing extension version: '+DeskChangerVersion);
}
<|endoftext|>


<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *    http: *www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
**/

'use strict';

describe('OCW Controllers', function() {

	beforeEach(module('ocw.controllers'));
	beforeEach(module('ocw.services'));

	describe('RcmedSelectionCtrl', function() {
		it('should automatically query RCMED on initialization', function() {
			inject(function($rootScope, $controller, $httpBackend) {
				$rootScope.baseURL = "http://localhost:8082"
				$httpBackend.expectJSONP($rootScope.baseURL + '/getObsDatasets?callback=JSON_CALLBACK').
					respond(200, [{longname: 1}, {longname: 2}]);

				var scope = $rootScope.$new();
				var ctrl = $controller("RcmedSelectionCtrl", {$scope: scope});
				$httpBackend.flush();

				expect(scope.availableObs.length).toBe(3);
				expect(scope.availableObs[0]).toEqual({longname: "Please select an option"});
				expect(scope.availableObs[1]).toEqual({longname: 1});
			});
		});

		it('should initialize the getObservations function', function() {
			inject(function($rootScope, $controller, $httpBackend) {
				$rootScope.baseURL = "http://localhost:8082"
				$httpBackend.expectJSONP($rootScope.baseURL + '/getObsDatasets?callback=JSON_CALLBACK').
					respond(200, [{longname: 1}, {longname: 2}]);

				var scope = $rootScope.$new();
				var ctrl = $controller("RcmedSelectionCtrl", {$scope: scope});
				$httpBackend.flush();

				expect(scope.availableObs.length).toBe(3);
				expect(scope.availableObs[0]).toEqual({longname: "Please select an option"});
				expect(scope.availableObs[1]).toEqual({longname: 1});

				// Set up a failed query
				$httpBackend.expectJSONP($rootScope.baseURL + '/getObsDatasets?callback=JSON_CALLBACK').
					respond(404);
				scope.getObservations();
				$httpBackend.flush();

				expect(scope.availableObs.length).toBe(1);
				expect(scope.availableObs[0]).toEqual('Unable to query RCMED');

			});
		});
	});
});
<commit_msg>CLIMATE-125 progress - Add getObservationTimeRange test<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *    http: *www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
**/

'use strict';

describe('OCW Controllers', function() {

	beforeEach(module('ocw.controllers'));
	beforeEach(module('ocw.services'));

	describe('RcmedSelectionCtrl', function() {
		it('should automatically query RCMED on initialization', function() {
			inject(function($rootScope, $controller, $httpBackend) {
				$rootScope.baseURL = "http://localhost:8082"
				$httpBackend.expectJSONP($rootScope.baseURL + '/getObsDatasets?callback=JSON_CALLBACK').
					respond(200, [{longname: 1}, {longname: 2}]);

				var scope = $rootScope.$new();
				var ctrl = $controller("RcmedSelectionCtrl", {$scope: scope});
				$httpBackend.flush();

				expect(scope.availableObs.length).toBe(3);
				expect(scope.availableObs[0]).toEqual({longname: "Please select an option"});
				expect(scope.availableObs[1]).toEqual({longname: 1});
			});
		});

		it('should initialize the getObservations function', function() {
			inject(function($rootScope, $controller, $httpBackend) {
				$rootScope.baseURL = "http://localhost:8082"
				$httpBackend.expectJSONP($rootScope.baseURL + '/getObsDatasets?callback=JSON_CALLBACK').
					respond(200, [{longname: 1}, {longname: 2}]);

				var scope = $rootScope.$new();
				var ctrl = $controller("RcmedSelectionCtrl", {$scope: scope});
				$httpBackend.flush();

				expect(scope.availableObs.length).toBe(3);
				expect(scope.availableObs[0]).toEqual({longname: "Please select an option"});
				expect(scope.availableObs[1]).toEqual({longname: 1});

				// Set up a failed query
				$httpBackend.expectJSONP($rootScope.baseURL + '/getObsDatasets?callback=JSON_CALLBACK').
					respond(404);
				scope.getObservations();
				$httpBackend.flush();

				expect(scope.availableObs.length).toBe(1);
				expect(scope.availableObs[0]).toEqual('Unable to query RCMED');

			});
		});

		it('should initialze the getObservationTimeRange function', function() {
			inject(function($rootScope, $controller, $httpBackend) {
				$rootScope.baseURL = "http://localhost:8082"
				$httpBackend.expectJSONP($rootScope.baseURL + '/getObsDatasets?callback=JSON_CALLBACK').
					respond(200, [{longname: 1}, {longname: 2}]);

				var scope = $rootScope.$new();
				var ctrl = $controller("RcmedSelectionCtrl", {$scope: scope});
				$httpBackend.flush();

				expect(scope.getObservationTimeRange(1)).toEqual({'start' : '1989-01-01 00:00:00',
				                                                    'end' : '2009-12-31 00:00:00'});
				expect(scope.getObservationTimeRange(-1)).toEqual(false);
			});
		});
	});
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import * as actions from '../../actions/filemgmt'
import { Sidebar, Menu, Icon } from 'semantic-ui-react'
class SideNav extends Component {
    constructor(props){
        super(props);

        this.state= {
            sideBarVisible:false,
            currentLocation : props.location.pathname
        }
    }

    toggleSideBarVisibility(){
        this.setState({
            sideBarVisible : !this.state.sideBarVisible
        })
    }

    componentWillMount() {
        if (this.props.auth && !this.props.auth.authenticated) {
          this.props.history.push(`/`);
        }
    }

    render() {

        if (typeof this.props.auth.user !== `undefined`) {
        return (

                <Sidebar.Pushable>
                    <Sidebar as={Menu} animation='scale down' width='thin' visible={this.state.sideBarVisible} icon='labeled' vertical inverted>
                        {this.props.auth.user.permissions.search(/VIEW_DASHBOARD|ALL/) >= 0 ? <Menu.Item name='home' active={this.state.currentLocation === '/dashboard'} as={Link} to={'/dashboard'}>
                            <Icon name='home' />
                            Home
                        </Menu.Item> : null }
                        {this.props.auth.user.permissions.search(/VIEW_USERS|ALL/) >= 0 ? <Menu.Item name='users' active={this.state.currentLocation === '/users'} as={Link} name='users' to={'/users'}>
                            <Icon name='users' />
                            Users
                        </Menu.Item> : null }
                        {this.props.auth.user.permissions.search(/UPGRADE_DEVICE|ALL/) >= 0 ? <Menu.Item name='upgrade' active={this.state.currentLocation === '/upgrade'} as={Link} to={'/upgrade'}>
                          <Icon name='up arrow' />
                          Upgrade
                        </Menu.Item> : null}
                        { this.props.auth.user.permissions.search(/VIEW_FILES|ALL/) >= 0 ? <Menu.Item name='filemgmt' active={this.state.currentLocation === '/filemgmt'} as={Link} to={'/filemgmt'}>
                          <Icon name='disk outline' />
                          File Management
                        </Menu.Item> : null }
                        { this.props.auth.user.permissions.search(/MODIFY_SSID|ALL/) >= 0 ? <Menu.Item name='modify_ssid' active={this.state.currentLocation === '/ssid/set'} as={Link} to={'/ssid/set'}>
                          <Icon name='wifi' />
                          Modify SSID
                        </Menu.Item> : null }
                        { this.props.auth.user.permissions.search(/CHANGE_CAPTIVE_PORTAL|ALL/) >= 0 ? <Menu.Item name='captive_mod' active={this.state.currentLocation === '/captive'} as={Link} to={'/captive'}>
                          <Icon name='eye' />
                          Modify Captive Portal
                        </Menu.Item> : null }

                        <Menu.Item name='logout' as={Link} to={'/'} onClick={(dispatch) => dispatch({ type: "DISABLE_AUTH"})}>
                            <Icon name='log out' />
                            Logout
                        </Menu.Item>
                    </Sidebar>
                    <Sidebar.Pusher>
                        <Menu fixed='top' inverted>
                            <Menu.Item name='bars' onClick={this.toggleSideBarVisibility.bind(this)}>
                                <Icon name='bars' />
                            </Menu.Item>
                        </Menu>
                        {this.props.children}
                    </Sidebar.Pusher>
                </Sidebar.Pushable>

        );
      } else {
        this.props.history.push("/");
        return null;
      }
    }
}

function mapStateToProps({ auth }) {
  return { auth }
}

export default withRouter(connect(mapStateToProps, actions)(SideNav));
<commit_msg>Call for logout action<commit_after>import React, { Component } from 'react';
import { Link, withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import * as actions from '../../actions/auth'
import { Sidebar, Menu, Icon } from 'semantic-ui-react'
class SideNav extends Component {
    constructor(props){
        super(props);

        this.state= {
            sideBarVisible:false,
            currentLocation : props.location.pathname
        }
    }

    toggleSideBarVisibility(){
        this.setState({
            sideBarVisible : !this.state.sideBarVisible
        })
    }

    handleLogout() {
        this.props.logout();
    }

    render() {
        if (typeof this.props.auth.user !== `undefined`) {
        return (

                <Sidebar.Pushable>
                    <Sidebar as={Menu} animation='scale down' width='thin' visible={this.state.sideBarVisible} icon='labeled' vertical inverted>
                        {this.props.auth.user.permissions.search(/VIEW_DASHBOARD|ALL/) >= 0 ? <Menu.Item name='home' active={this.state.currentLocation === '/dashboard'} as={Link} to={'/dashboard'}>
                            <Icon name='home' />
                            Home
                        </Menu.Item> : null }
                        {this.props.auth.user.permissions.search(/VIEW_USERS|ALL/) >= 0 ? <Menu.Item name='users' active={this.state.currentLocation === '/users'} as={Link} name='users' to={'/users'}>
                            <Icon name='users' />
                            Users
                        </Menu.Item> : null }
                        {this.props.auth.user.permissions.search(/UPGRADE_DEVICE|ALL/) >= 0 ? <Menu.Item name='upgrade' active={this.state.currentLocation === '/upgrade'} as={Link} to={'/upgrade'}>
                          <Icon name='up arrow' />
                          Upgrade
                        </Menu.Item> : null}
                        { this.props.auth.user.permissions.search(/VIEW_FILES|ALL/) >= 0 ? <Menu.Item name='filemgmt' active={this.state.currentLocation === '/filemgmt'} as={Link} to={'/filemgmt'}>
                          <Icon name='disk outline' />
                          File Management
                        </Menu.Item> : null }
                        { this.props.auth.user.permissions.search(/MODIFY_SSID|ALL/) >= 0 ? <Menu.Item name='modify_ssid' active={this.state.currentLocation === '/ssid/set'} as={Link} to={'/ssid/set'}>
                          <Icon name='wifi' />
                          Modify SSID
                        </Menu.Item> : null }
                        { this.props.auth.user.permissions.search(/CHANGE_CAPTIVE_PORTAL|ALL/) >= 0 ? <Menu.Item name='captive_mod' active={this.state.currentLocation === '/captive'} as={Link} to={'/captive'}>
                          <Icon name='eye' />
                          Modify Captive Portal
                        </Menu.Item> : null }

                        <Menu.Item name='logout' as={Link} to={'/'} onClick={this.handleLogout.bind(this)}>
                            <Icon name='log out' />
                            Logout
                        </Menu.Item>
                    </Sidebar>
                    <Sidebar.Pusher>
                        <Menu fixed='top' inverted>
                            <Menu.Item name='bars' onClick={this.toggleSideBarVisibility.bind(this)}>
                                <Icon name='bars' />
                            </Menu.Item>
                        </Menu>
                        {this.props.children}
                    </Sidebar.Pusher>
                </Sidebar.Pushable>

        );
      } else {
        return null;
      }
    }
}

function mapStateToProps({ auth }) {
  return { auth }
}

export default withRouter(connect(mapStateToProps, actions)(SideNav));
<|endoftext|>


<commit_before>/*
 *  Copyright 2014 TWO SIGMA OPEN SOURCE, LLC
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
/**
 * notebook sharing menu item
 * Add an item to the notebook menu to share it via the web
 */
define(function(require, exports, module) {
  "use strict";
  var publishToWeb = function(scope) {
    bkHelper.httpPost("../beaker/rest/publish/github", {
      type: "notebook",
      json: angular.toJson(scope.getShareData())
    })
    .success(function(reply) {
      window.open(reply);
    })
    .error(function(msg) {
      bkHelper.show1ButtonModal(msg, "Publish Failed");
    });
  };
  var plugin = function(scope) {
    return [
      {
        name: "public web...",
        tooltip: "using an anonymous github gist",
        action: function() {
          publishToWeb(scope);
        }
      }];
  };
  exports.cellType = ["notebook", "section", "code"];
  exports.plugin = plugin;
});
<commit_msg>Show loading status when sharing to web<commit_after>/*
 *  Copyright 2014 TWO SIGMA OPEN SOURCE, LLC
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
/**
 * notebook sharing menu item
 * Add an item to the notebook menu to share it via the web
 */
define(function(require, exports, module) {
  "use strict";
  var publishToWeb = function(scope) {
    bkHelper.showStatus('Sharing to Web');
    bkHelper.httpPost("../beaker/rest/publish/github", {
      type: "notebook",
      json: angular.toJson(scope.getShareData())
    })
    .success(function(reply) {
      bkHelper.clearStatus('Sharing to Web');
      window.open(reply);
    })
    .error(function(msg) {
      bkHelper.clearStatus('Sharing to Web');
      bkHelper.show1ButtonModal(msg, "Publish Failed");
    });
  };
  var plugin = function(scope) {
    return [
      {
        name: "public web...",
        tooltip: "using an anonymous github gist",
        action: function() {
          publishToWeb(scope);
        }
      }];
  };
  exports.cellType = ["notebook", "section", "code"];
  exports.plugin = plugin;
});
<|endoftext|>


<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var _ = require('lodash');
var async = require('async');
var sundial = require('sundial');
var struct = require('../../struct.js')();
var annotate = require('../../eventAnnotations');

var TZOUtil = require('../../TimezoneOffsetUtil');

var debug = require('bows')('FreeStyleLiteDriver');

module.exports = function (config) {
  var cfg = _.clone(config);
  cfg.deviceData = null;
  var serialDevice = config.deviceComms;

  // With no date & time settings changes available,
  // timezone is applied across-the-board
  cfg.tzoUtil = new TZOUtil(cfg.timezone, new Date().toISOString(), []);

  var STX = 0x02;
  var ETX = 0x03;
  var CR = 0x0D;
  var LF = 0x0A;

  var dateTimePattern = '([A-Za-z ]{4} [0-3][0-9] [0-9]{4} ' +
                        '[012][0-9]:[0-5][0-9](?::[0-6][0-9])?)';
  function parseDateTime(match, index) {
    var fmt = 'MMMM DD YYYY HH:mm:ss';
    var parsed = sundial.parseFromFormat(match[index], fmt);
    var dev = sundial.formatDeviceTime(parsed);
    var utcInfo = cfg.tzoUtil.lookup(parsed);
    return {
      dev: dev,
      utc: utcInfo.time,
      timezoneOffset: utcInfo.timezoneOffset,
      conversionOffset: utcInfo.conversionOffset
    };
  }

  var patterns = {
    serialNumber: {
      pattern: '^(?:[A-Z]-....-.....|[A-Z][A-Z][A-Z]....-.....|[XC][A-Z][GM]........)$',
      handler: function(match, packet) {
        packet.serialNumber = match[0];
      }
    },
    softwareVersion: {
      pattern: '^[0-9.]{4,13}',
      handler: function(match, packet) {
        packet.softwareVersion = match[0];
      }
    },
    deviceTime: {
      pattern: '^' + dateTimePattern + '$',
      handler: function(match, packet) {
        var times = parseDateTime(match, 1);
        packet.deviceTime = times.dev;
        packet.time = times.utc;
      }
    },
    numEntries: {
      pattern: '^(?:[0-9]{3}|Log Empty)$',
      handler: function(match, packet) {
        packet.numEntries = parseInt(match[0], 10);
        if (_.isNaN(packet.numEntries)) {
          packet.numEntries = 0;
        }
      }
    },
    notFound: {
      pattern: '^Log Not Found$',
      handler: function(match, packet) {
        packet.notFound = true;
      }
    },
    end: {
      pattern: '^END$',
      handler: function(match, packet) {
        packet.end = true;
      }
    },
    ok: {
      pattern: '^CMD OK$',
      handler: function(match, packet) {
        packet.ok = true;
      }
    },
    fail: {
      pattern: '^CMD Fail!$',
      handler: function(match, packet) {
        packet.fail = true;
      }
    },
    logEntry: {
      pattern: '^\s*([0-9HL][0-9OI][0-9 ])  ' + dateTimePattern + ' [0-9A-Fa-f][0-9A-Fa-f] (0x[0-9A-Fa-f][0-9A-Fa-f])',
      handler: function(match, packet) {
        var entry = {};
        var reading = match[1];

        var testType = match[3];

        if(testType[3] === '1') {
          debug('Marking as control test');
          entry.control = true;
        }else{
          entry.control = false;
        }

        //the HI/LO glucose range for all FreeStyle meters are 20-500 mg/dL according to spec
        if (reading == 'HI ') {
            entry.reading = 501;
            entry.annotations = [{
              code: 'bg/out-of-range',
              value: 'high',
              threshold: 500
            }];
        } else if (reading == 'LO ') {
          entry.reading = 19;
          entry.annotations = [{
            code: 'bg/out-of-range',
            value: 'low',
            threshold: 20
          }];
        } else {
          entry.reading = parseInt(reading, 10);
        }

        entry.datetime = parseDateTime(match, 2);
        packet.logEntries.push(entry);
      }
    },
    checksum: {
      pattern: '^(0x[0-9A-F]{4})  END',
      handler: function(match, packet) {
        packet.receivedChecksum = parseInt(match[1], 16);
      }
    }
  };
  var logEntry = 'logEntry';

  var extractPacket = function (bytes) {
    var packet = {
      bytes: bytes,
      valid: false,
      packet_len: 0,
      lcb: 0,
      lines: [],
      logEntries: []
    };

    var startIndex = 0;
    // the SerialPort library does not pass on STX and ETX, so we look for the
    // 'END' characters to determine when to stop
    var endIndex = searchForSequence(bytes, [0x45, 0x4E, 0x44]);
    if (endIndex === -1) {
      return packet;
    } else {
      endIndex += 2;
    }

    function stringFromBytes(bytes, start, end) {
      var s = '';
      for (var i=start; i<end; ++i) {
        s += String.fromCharCode(bytes[i]);
      }
      return s;
    }

    function searchForSequence(bytes, sequence) {
      var found = false;
      for (var ix=0; ix<bytes.length; ++ix) {
        found = true;
        for (var jx=0; jx<sequence.length; ++jx) {
          if (bytes[ix + jx] != sequence[jx]) {
            found = false;
            break;
          }
        }
        if (found) {
          return ix;
        }
      }
      return -1;
    }

    var checksumIndex = searchForSequence(bytes, [0x0D, 0x0A, 0x30, 0x78]);
    checksumIndex += 2;
    if (checksumIndex !== -1) {
      var sum = 0;
      for (var i=startIndex; i<checksumIndex; ++i) {
        sum += bytes[i];
      }
      packet.calculatedChecksum = sum & 0xFFFF;
    }

    packet.packet_len = endIndex;

    var startLine = startIndex;
    while (startLine < endIndex) {
      var endLine = _.indexOf(bytes, CR, startLine);
      if (endLine === -1) {
        endLine = endIndex - 1;
      }
      if (bytes[endLine + 1] != LF) {
        packet.packet_len = endLine;
        return packet;
      }

      var line = stringFromBytes(bytes, startLine, endLine);
      packet.lines.push(line);
      startLine = endLine + 2;
    }

    var patkeys = _.keys(patterns);
    _.remove(patkeys, function(k) { return k === logEntry; });
    patkeys.unshift(logEntry);

    _.forEach(packet.lines, function(line, index) {
      var found = false;
      _.forEach(patkeys, function(key) {
        var m = line.trim().match(patterns[key].pattern);
        if (m) {
          found = true;
          patterns[key].handler(m, packet);
        }
      });
      if (!found && line !== '') {
        debug('No pattern match found for "' + line + '"');
      }
    });

    packet.valid = true;
    if (packet.receivedChecksum && packet.receivedChecksum != packet.calculatedChecksum) {
      debug('Failed checksum! rcv: ', packet.receivedChecksum, ' calc: ', packet.calculatedChecksum);
      packet.valid = false;
    }
    if (packet.numEntries && packet.logEntries.length !== packet.numEntries) {
      debug('Entry count failure! num: ', packet.numEntries, ' found: ', packet.logEntries.length);
      packet.valid = false;
    }
    if (packet.fail) {
      packet.valid = false;
    }
    return packet;
  };

  var freeStyleErrorHandler = function(info) {
    if (info.connectionId && info.error) {
      if (info.error == 'timeout') {
        return [ ETX ];
      }
    }
  };

  var freeStyleMessageHandler = function (buffer) {
    var discardCount = 0;
    while (buffer.len() > discardCount && buffer.get(0) == ETX) {
      ++discardCount;
    }

    if (discardCount) {
      buffer.discard(discardCount);
    }

    if (buffer.len() < 11) {
      return false;
    }

    var packet = extractPacket(buffer.bytes());
    if (packet.packet_len !== 0) {
      buffer.discard(packet.packet_len);
    }

    if (packet.valid) {
      return packet;
    } else {
      return null;
    }
  };

  var listenForPacket = function (timeout, parser, callback) {
    var abortTimer = setTimeout(function () {
      clearInterval(listenTimer);
      debug('TIMEOUT');
      callback('TIMEOUT', null);
    }, timeout);

    var listenTimer = setInterval(function () {
      if (serialDevice.hasAvailablePacket()) {
        var pkt = serialDevice.nextPacket();
        clearTimeout(abortTimer);
        clearInterval(listenTimer);
        pkt.parsed_payload = parser(pkt);
        callback(null, pkt);
      }
    }, 100); // 100ms should be enough to be kind to older CPUs
  };

  var freeStyleCommandResponse = function (commandpacket, callback) {
    serialDevice.writeSerial(commandpacket.packet, function (err) {
      if (err) {
        callback(err, null);
      }
      listenForPacket(30000, commandpacket.parser, function(err, result) {
        if (err) {
          callback(err, null);
        } else {
          callback(err, result);
        }
      });
    });
  };

  var xGetData = function() {
    return {
      packet: struct.packString('mem'),
      parser: function (result) {
        return result;
      }
    };
  };

  var getAllData = function (obj, cb) {
    var cmd = xGetData();
    freeStyleCommandResponse(cmd, function (err, result) {
      if (err) {
        debug('Failure trying to talk to device.');
        debug(err);
        debug(result);
        cb(err, null);
      } else {
        _.assign(obj, result);
        cb(null, obj);
      }
    });
  };

  var prepBGData = function (progress, data) {
    cfg.builder.setDefaults({ deviceId: data.id });
    var dataToPost = [];
    for (var i = 0; i < data.logEntries.length; ++i) {
      var datum = data.logEntries[i];

        if(datum.control === true) {
          debug('Discarding control');
          continue;
        }

        var smbg = cfg.builder.makeSMBG()
          .with_value(datum.reading)
          .with_deviceTime(datum.datetime.dev)
          .with_timezoneOffset(datum.datetime.timezoneOffset)
          .with_conversionOffset(datum.datetime.conversionOffset)
          .with_time(datum.datetime.utc)
          .with_units('mg/dL')
          .set('index', i)
          .done();
        if (datum.annotations) {
          _.each(datum.annotations, function(ann) {
            annotate.annotateEvent(smbg, ann);
          });
        }
        dataToPost.push(smbg);
    }
    return dataToPost;
  };

  return {
    // using the default detect for this driver
    // detect: function(cb) {
    // },

    // this function starts the chain, so it has to create but not accept
    // the result (data) object; it's then passed down the rest of the chain
    setup: function (deviceInfo, progress, cb) {
      debug('in setup!');
      progress(100);
      cb(null, {deviceInfo: deviceInfo});
    },

    connect: function (progress, data, cb) {
      debug('in connect!');
      var handlers = {
        packetHandler: freeStyleMessageHandler,
        errorHandler: freeStyleErrorHandler
      };

      cfg.deviceComms.connect(data.deviceInfo, handlers, function(err) {
        if (err) {
          return cb(err);
        }
        progress(100);
        data.connect = true;
        cb(null, data);
      });
    },

    getConfigInfo: function (progress, data, cb) {
      progress(100);
      cb(null, data);
    },

    fetchData: function (progress, data, cb) {
      debug('in fetchData');
      getAllData({}, function (err, result) {
        progress(100);
        data.connect = true;
        debug(result);
        _.extend(data, _.pick(result, 'serialNumber', 'softwareVersion', 'deviceTime'));

        if (data.serialNumber[0] === 'C') {
          // there are a wide variety of Abbott meters that start with C and D,
          // but this is the only way to distinguish the FreeStyle Freedom Lite
          // and the FreeStyle Lite as they use the same cable, so the USB PID/VID
          // is the same.
          data.model = 'AbbottFreeStyleFreedomLite';
        } else if (data.serialNumber[0] === 'D'){
          data.model = 'AbbottFreeStyleLite';
        } else {
          data.model = 'UnknownFreeStyle';
        }
        debug('Detected as: ', data.model);

        data.id = data.model + ' ' + data.serialNumber;
        data.logEntries = result.logEntries;
        data.numEntries = result.numEntries;
        cb(null, data);
      });
    },

    processData: function (progress, data, cb) {
      progress(0);
      data.post_records = _.map(prepBGData(progress, data), function(d) {
        delete d.index;
        return d;
      });
      progress(100);
      data.processData = true;
      debug(data);
      cb(null, data);
    },

    uploadData: function (progress, data, cb) {
      progress(0);

      var sessionInfo = {
        deviceTags: ['bgm'],
        deviceManufacturers: ['Abbott'],
        deviceModel: data.model,
        deviceSerialNumber: data.serialNumber,
        deviceId: data.id,
        start: sundial.utcDateString(),
        timeProcessing: cfg.tzoUtil.type,
        tzName : cfg.timezone,
        version: cfg.version
      };

      cfg.api.upload.toPlatform(data.post_records, sessionInfo, progress, cfg.groupId, function (err, result) {
        if (err) {
          debug(err);
          progress(100);
          return cb(err, data);
        } else {
          progress(100);
          return cb(null, data);
        }
      });
    },

    disconnect: function (progress, data, cb) {
      progress(100);
      data.disconnect = true;
      cb(null, data);
    },

    cleanup: function (progress, data, cb) {
      cfg.deviceComms.clearPacketHandler();
      cfg.deviceComms.clearErrorHandler();
      cfg.deviceComms.disconnect(function() {
        progress(100);
        data.cleanup = true;
        cb(null, data);
      });
    },

    testDriver: function(config) {
      var progress = function(v) {
        debug('progress: ', v);
      };
      var data = {};
      this.connect(progress, data, function(err, result) {
        debug('result:', result);
      });
    }
  };

};
<commit_msg>check freestyle lite and freedom lite against server time<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var _ = require('lodash');
var async = require('async');
var sundial = require('sundial');
var struct = require('../../struct.js')();
var annotate = require('../../eventAnnotations');
var common = require('../../commonFunctions');

var TZOUtil = require('../../TimezoneOffsetUtil');

var debug = require('bows')('FreeStyleLiteDriver');

module.exports = function (config) {
  var cfg = _.clone(config);
  cfg.deviceData = null;
  var serialDevice = config.deviceComms;

  // With no date & time settings changes available,
  // timezone is applied across-the-board
  cfg.tzoUtil = new TZOUtil(cfg.timezone, new Date().toISOString(), []);

  var STX = 0x02;
  var ETX = 0x03;
  var CR = 0x0D;
  var LF = 0x0A;

  var dateTimePattern = '([A-Za-z ]{4} [0-3][0-9] [0-9]{4} ' +
                        '[012][0-9]:[0-5][0-9](?::[0-6][0-9])?)';
  function parseDateTime(match, index) {
    var fmt = 'MMMM DD YYYY HH:mm:ss';
    var parsed = sundial.parseFromFormat(match[index], fmt);
    var dev = sundial.formatDeviceTime(parsed);
    var utcInfo = cfg.tzoUtil.lookup(parsed);
    return {
      dev: dev,
      utc: utcInfo.time,
      timezoneOffset: utcInfo.timezoneOffset,
      conversionOffset: utcInfo.conversionOffset
    };
  }

  var patterns = {
    serialNumber: {
      pattern: '^(?:[A-Z]-....-.....|[A-Z][A-Z][A-Z]....-.....|[XC][A-Z][GM]........)$',
      handler: function(match, packet) {
        packet.serialNumber = match[0];
      }
    },
    softwareVersion: {
      pattern: '^[0-9.]{4,13}',
      handler: function(match, packet) {
        packet.softwareVersion = match[0];
      }
    },
    deviceTime: {
      pattern: '^' + dateTimePattern + '$',
      handler: function(match, packet) {
        var times = parseDateTime(match, 1);
        packet.deviceTime = times.dev;
        packet.time = times.utc;
      }
    },
    numEntries: {
      pattern: '^(?:[0-9]{3}|Log Empty)$',
      handler: function(match, packet) {
        packet.numEntries = parseInt(match[0], 10);
        if (_.isNaN(packet.numEntries)) {
          packet.numEntries = 0;
        }
      }
    },
    notFound: {
      pattern: '^Log Not Found$',
      handler: function(match, packet) {
        packet.notFound = true;
      }
    },
    end: {
      pattern: '^END$',
      handler: function(match, packet) {
        packet.end = true;
      }
    },
    ok: {
      pattern: '^CMD OK$',
      handler: function(match, packet) {
        packet.ok = true;
      }
    },
    fail: {
      pattern: '^CMD Fail!$',
      handler: function(match, packet) {
        packet.fail = true;
      }
    },
    logEntry: {
      pattern: '^\s*([0-9HL][0-9OI][0-9 ])  ' + dateTimePattern + ' [0-9A-Fa-f][0-9A-Fa-f] (0x[0-9A-Fa-f][0-9A-Fa-f])',
      handler: function(match, packet) {
        var entry = {};
        var reading = match[1];

        var testType = match[3];

        if(testType[3] === '1') {
          debug('Marking as control test');
          entry.control = true;
        }else{
          entry.control = false;
        }

        //the HI/LO glucose range for all FreeStyle meters are 20-500 mg/dL according to spec
        if (reading == 'HI ') {
            entry.reading = 501;
            entry.annotations = [{
              code: 'bg/out-of-range',
              value: 'high',
              threshold: 500
            }];
        } else if (reading == 'LO ') {
          entry.reading = 19;
          entry.annotations = [{
            code: 'bg/out-of-range',
            value: 'low',
            threshold: 20
          }];
        } else {
          entry.reading = parseInt(reading, 10);
        }

        entry.datetime = parseDateTime(match, 2);
        packet.logEntries.push(entry);
      }
    },
    checksum: {
      pattern: '^(0x[0-9A-F]{4})  END',
      handler: function(match, packet) {
        packet.receivedChecksum = parseInt(match[1], 16);
      }
    }
  };
  var logEntry = 'logEntry';

  var extractPacket = function (bytes) {
    var packet = {
      bytes: bytes,
      valid: false,
      packet_len: 0,
      lcb: 0,
      lines: [],
      logEntries: []
    };

    var startIndex = 0;
    // the SerialPort library does not pass on STX and ETX, so we look for the
    // 'END' characters to determine when to stop
    var endIndex = searchForSequence(bytes, [0x45, 0x4E, 0x44]);
    if (endIndex === -1) {
      return packet;
    } else {
      endIndex += 2;
    }

    function stringFromBytes(bytes, start, end) {
      var s = '';
      for (var i=start; i<end; ++i) {
        s += String.fromCharCode(bytes[i]);
      }
      return s;
    }

    function searchForSequence(bytes, sequence) {
      var found = false;
      for (var ix=0; ix<bytes.length; ++ix) {
        found = true;
        for (var jx=0; jx<sequence.length; ++jx) {
          if (bytes[ix + jx] != sequence[jx]) {
            found = false;
            break;
          }
        }
        if (found) {
          return ix;
        }
      }
      return -1;
    }

    var checksumIndex = searchForSequence(bytes, [0x0D, 0x0A, 0x30, 0x78]);
    checksumIndex += 2;
    if (checksumIndex !== -1) {
      var sum = 0;
      for (var i=startIndex; i<checksumIndex; ++i) {
        sum += bytes[i];
      }
      packet.calculatedChecksum = sum & 0xFFFF;
    }

    packet.packet_len = endIndex;

    var startLine = startIndex;
    while (startLine < endIndex) {
      var endLine = _.indexOf(bytes, CR, startLine);
      if (endLine === -1) {
        endLine = endIndex - 1;
      }
      if (bytes[endLine + 1] != LF) {
        packet.packet_len = endLine;
        return packet;
      }

      var line = stringFromBytes(bytes, startLine, endLine);
      packet.lines.push(line);
      startLine = endLine + 2;
    }

    var patkeys = _.keys(patterns);
    _.remove(patkeys, function(k) { return k === logEntry; });
    patkeys.unshift(logEntry);

    _.forEach(packet.lines, function(line, index) {
      var found = false;
      _.forEach(patkeys, function(key) {
        var m = line.trim().match(patterns[key].pattern);
        if (m) {
          found = true;
          patterns[key].handler(m, packet);
        }
      });
      if (!found && line !== '') {
        debug('No pattern match found for "' + line + '"');
      }
    });

    packet.valid = true;
    if (packet.receivedChecksum && packet.receivedChecksum != packet.calculatedChecksum) {
      debug('Failed checksum! rcv: ', packet.receivedChecksum, ' calc: ', packet.calculatedChecksum);
      packet.valid = false;
    }
    if (packet.numEntries && packet.logEntries.length !== packet.numEntries) {
      debug('Entry count failure! num: ', packet.numEntries, ' found: ', packet.logEntries.length);
      packet.valid = false;
    }
    if (packet.fail) {
      packet.valid = false;
    }
    return packet;
  };

  var freeStyleErrorHandler = function(info) {
    if (info.connectionId && info.error) {
      if (info.error == 'timeout') {
        return [ ETX ];
      }
    }
  };

  var freeStyleMessageHandler = function (buffer) {
    var discardCount = 0;
    while (buffer.len() > discardCount && buffer.get(0) == ETX) {
      ++discardCount;
    }

    if (discardCount) {
      buffer.discard(discardCount);
    }

    if (buffer.len() < 11) {
      return false;
    }

    var packet = extractPacket(buffer.bytes());
    if (packet.packet_len !== 0) {
      buffer.discard(packet.packet_len);
    }

    if (packet.valid) {
      return packet;
    } else {
      return null;
    }
  };

  var listenForPacket = function (timeout, parser, callback) {
    var abortTimer = setTimeout(function () {
      clearInterval(listenTimer);
      debug('TIMEOUT');
      callback('TIMEOUT', null);
    }, timeout);

    var listenTimer = setInterval(function () {
      if (serialDevice.hasAvailablePacket()) {
        var pkt = serialDevice.nextPacket();
        clearTimeout(abortTimer);
        clearInterval(listenTimer);
        pkt.parsed_payload = parser(pkt);
        callback(null, pkt);
      }
    }, 100); // 100ms should be enough to be kind to older CPUs
  };

  var freeStyleCommandResponse = function (commandpacket, callback) {
    serialDevice.writeSerial(commandpacket.packet, function (err) {
      if (err) {
        callback(err, null);
      }
      listenForPacket(30000, commandpacket.parser, function(err, result) {
        if (err) {
          callback(err, null);
        } else {
          callback(err, result);
        }
      });
    });
  };

  var xGetData = function() {
    return {
      packet: struct.packString('mem'),
      parser: function (result) {
        return result;
      }
    };
  };

  var getAllData = function (obj, cb) {
    var cmd = xGetData();
    freeStyleCommandResponse(cmd, function (err, result) {
      if (err) {
        debug('Failure trying to talk to device.');
        debug(err);
        debug(result);
        cb(err, null);
      } else {
        _.assign(obj, result);
        cb(null, obj);
      }
    });
  };

  var prepBGData = function (progress, data) {
    cfg.builder.setDefaults({ deviceId: data.id });
    var dataToPost = [];
    for (var i = 0; i < data.logEntries.length; ++i) {
      var datum = data.logEntries[i];

        if(datum.control === true) {
          debug('Discarding control');
          continue;
        }

        var smbg = cfg.builder.makeSMBG()
          .with_value(datum.reading)
          .with_deviceTime(datum.datetime.dev)
          .with_timezoneOffset(datum.datetime.timezoneOffset)
          .with_conversionOffset(datum.datetime.conversionOffset)
          .with_time(datum.datetime.utc)
          .with_units('mg/dL')
          .set('index', i)
          .done();
        if (datum.annotations) {
          _.each(datum.annotations, function(ann) {
            annotate.annotateEvent(smbg, ann);
          });
        }
        dataToPost.push(smbg);
    }
    return dataToPost;
  };

  return {
    // using the default detect for this driver
    // detect: function(cb) {
    // },

    // this function starts the chain, so it has to create but not accept
    // the result (data) object; it's then passed down the rest of the chain
    setup: function (deviceInfo, progress, cb) {
      debug('in setup!');
      progress(100);
      cb(null, {deviceInfo: deviceInfo});
    },

    connect: function (progress, data, cb) {
      debug('in connect!');
      var handlers = {
        packetHandler: freeStyleMessageHandler,
        errorHandler: freeStyleErrorHandler
      };

      cfg.deviceComms.connect(data.deviceInfo, handlers, function(err) {
        if (err) {
          return cb(err);
        }
        progress(100);
        data.connect = true;
        cb(null, data);
      });
    },

    getConfigInfo: function (progress, data, cb) {
      progress(100);
      cb(null, data);
    },

    fetchData: function (progress, data, cb) {
      debug('in fetchData');
      getAllData({}, function (err, result) {
        if (err) {
          return cb(err, null);
        }

        progress(100);
        data.connect = true;
        debug(result);
        _.extend(data, _.pick(result, 'serialNumber', 'softwareVersion', 'deviceTime'));

        if (data.serialNumber[0] === 'C') {
          // there are a wide variety of Abbott meters that start with C and D,
          // but this is the only way to distinguish the FreeStyle Freedom Lite
          // and the FreeStyle Lite as they use the same cable, so the USB PID/VID
          // is the same.
          data.model = 'AbbottFreeStyleFreedomLite';
        } else if (data.serialNumber[0] === 'D'){
          data.model = 'AbbottFreeStyleLite';
        } else {
          data.model = 'UnknownFreeStyle';
        }
        debug('Detected as: ', data.model);

        data.id = data.model + ' ' + data.serialNumber;
        data.logEntries = result.logEntries;
        data.numEntries = result.numEntries;

        common.checkDeviceTime(data.deviceTime, cfg.timezone, function(checkErr) {
          cb(checkErr, data);
        });
      });
    },

    processData: function (progress, data, cb) {
      progress(0);
      data.post_records = _.map(prepBGData(progress, data), function(d) {
        delete d.index;
        return d;
      });
      progress(100);
      data.processData = true;
      debug(data);
      cb(null, data);
    },

    uploadData: function (progress, data, cb) {
      progress(0);

      var sessionInfo = {
        deviceTags: ['bgm'],
        deviceManufacturers: ['Abbott'],
        deviceModel: data.model,
        deviceSerialNumber: data.serialNumber,
        deviceId: data.id,
        start: sundial.utcDateString(),
        timeProcessing: cfg.tzoUtil.type,
        tzName : cfg.timezone,
        version: cfg.version
      };

      cfg.api.upload.toPlatform(data.post_records, sessionInfo, progress, cfg.groupId, function (err, result) {
        if (err) {
          debug(err);
          progress(100);
          return cb(err, data);
        } else {
          progress(100);
          return cb(null, data);
        }
      });
    },

    disconnect: function (progress, data, cb) {
      progress(100);
      data.disconnect = true;
      cb(null, data);
    },

    cleanup: function (progress, data, cb) {
      cfg.deviceComms.clearPacketHandler();
      cfg.deviceComms.clearErrorHandler();
      cfg.deviceComms.disconnect(function() {
        progress(100);
        data.cleanup = true;
        cb(null, data);
      });
    },

    testDriver: function(config) {
      var progress = function(v) {
        debug('progress: ', v);
      };
      var data = {};
      this.connect(progress, data, function(err, result) {
        debug('result:', result);
      });
    }
  };

};
<|endoftext|>


<commit_before>// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'

angular.module('careWheels', [
  'ionic',
  'ui.router',
  'ngCordova',
  'FredrikSandell.worker-pool',
  'angularMoment',
  'careWheels.fileloggermodule'
])

//contant definition for endpoint base url
.constant('BASE_URL', 'https://carewheels.cecs.pdx.edu:8443')

// change the version number here
.constant('VERSION_NUMBER', '0.10')

.run(function ($rootScope, $ionicPlatform, $ionicHistory, $state, $window, User) {

  $rootScope.$on('$stateChangeStart', function (event, next, nextParams, fromState) {
    console.log('state change');
    if (User.credentials() === null) {
      if (next.name !== 'login') {
        event.preventDefault();
        $state.go('login');
      }
    }
  });

  $ionicPlatform.registerBackButtonAction(function (event) {
    console.log("in registerbackbutton");
    console.log($ionicHistory.backTitle());
    $state.go($ionicHistory.backTitle());
  }, 100);

  $ionicPlatform.ready(function () {
    if (window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
      cordova.plugins.Keyboard.disableScroll(true);
    }
    if (window.StatusBar) {
      StatusBar.styleDefault();
    }
  });

})

// API factory for making all php endpoints globally accessible.
.factory('API', function (BASE_URL) {
  var api = {
    userAndGroupInfo: BASE_URL + '/userandgroupmemberinfo.php',
    userInfo: BASE_URL + '/userinfo.php',
    updateUserReminders: BASE_URL + '/updateuserreminders.php',
    groupMemberInfo: BASE_URL + '/groupmemberinfo.php',
    updateLastOwnership: BASE_URL + '/updatelastownershiptakentime.php',
    creditUser: BASE_URL + '/credituser.php',
    updateSettings:BASE_URL + '/updatesettings.php'
  };
  return api;
});
<commit_msg>Changing version number to 1.00, to signify that the capstone team has finished working on it.<commit_after>// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'

angular.module('careWheels', [
  'ionic',
  'ui.router',
  'ngCordova',
  'FredrikSandell.worker-pool',
  'angularMoment',
  'careWheels.fileloggermodule'
])

//contant definition for endpoint base url
.constant('BASE_URL', 'https://carewheels.cecs.pdx.edu:8443')

// change the version number here
.constant('VERSION_NUMBER', '1.00')

.run(function ($rootScope, $ionicPlatform, $ionicHistory, $state, $window, User) {

  $rootScope.$on('$stateChangeStart', function (event, next, nextParams, fromState) {
    console.log('state change');
    if (User.credentials() === null) {
      if (next.name !== 'login') {
        event.preventDefault();
        $state.go('login');
      }
    }
  });

  $ionicPlatform.registerBackButtonAction(function (event) {
    console.log("in registerbackbutton");
    console.log($ionicHistory.backTitle());
    $state.go($ionicHistory.backTitle());
  }, 100);

  $ionicPlatform.ready(function () {
    if (window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
      cordova.plugins.Keyboard.disableScroll(true);
    }
    if (window.StatusBar) {
      StatusBar.styleDefault();
    }
  });

})

// API factory for making all php endpoints globally accessible.
.factory('API', function (BASE_URL) {
  var api = {
    userAndGroupInfo: BASE_URL + '/userandgroupmemberinfo.php',
    userInfo: BASE_URL + '/userinfo.php',
    updateUserReminders: BASE_URL + '/updateuserreminders.php',
    groupMemberInfo: BASE_URL + '/groupmemberinfo.php',
    updateLastOwnership: BASE_URL + '/updatelastownershiptakentime.php',
    creditUser: BASE_URL + '/credituser.php',
    updateSettings:BASE_URL + '/updatesettings.php'
  };
  return api;
});
<|endoftext|>