<commit_before>(function() {
  var TileMap = function(options) {
    this.canvas = options.canvas;
    this.context = this.canvas.getContext('2d');
    this.map = options.map;
    this.entities = options.entities || [],

    // Add each image to the appropriate tileset and patch a function to verify
    // it's index boundaries
    this.map.tilesets.forEach(function(tileset, index, tilesets) {
      var tiles = Math.floor(tileset.imagewidth / tileset.tilewidth) *
                  Math.floor(tileset.imageheight / tileset.tileheight);

      tilesets[index].image = options.images[index];
      tilesets[index].min = tileset.firstgid - 1;
      tilesets[index].max = tilesets[index].min + tiles;

      // Check whether the 'number' is in this tileset or not
      tilesets[index].inside = function(number) {
        return number >= this.min && this.max >= number;
      }.bind(tileset);
      tilesets[index].getZIndex = function() {
        return this.properties * this.map.tileheight;
      }.bind(tileset);
    });

    // Normalize layers to a zero indexed map
    var map = this.map;
    this.map.layers.forEach(function(layer, index, layers) {
      layer.data.forEach(function(number, index, layer) {
        layer[index] = number - 1;
      });
      layers[index].getZIndex = function() {
        var top = parseFloat(this.properties.zIndex || -999) * map.tileheight;
        return top + map.tileheight;
      }.bind(layers[index]);
    });
  }

  TileMap.prototype.clip = function(index) {
    var tileset = null;
    this.map.tilesets.forEach(function(set) {
      if (set.inside(index)) {
        tileset = set;
      }
    });
    var row = Math.floor((index * tileset.tilewidth) / tileset.imagewidth);

    return {
      image: tileset.image,
      x: (index * tileset.tilewidth) % tileset.imagewidth,
      y: (row * tileset.tileheight) % tileset.imageheight,
      width: tileset.tilewidth,
      height: tileset.tileheight,
    }
  }

  TileMap.prototype.draw = function(offsetX, offsetY) {
    var pendingEntities = this.entities.slice(0);
    this.map.layers.forEach(function(layer) {
      layer.data.forEach(function(number, index) {
        if (number < 0) {
          return;
        }

        var x = (index % layer.width);
        var y = Math.floor(index / layer.width);
        var clip = this.clip(number);
        this.context.drawImage(clip.image, clip.x, clip.y, clip.width, clip.height,
                               (x * Map.tilewidth) + offsetX, (y * Map.tileheight) + offsetY,
                               Map.tilewidth, Map.tileheight);
      }, this);
      pendingEntities.forEach(function(entity, index, pending) {
        if (layer.getZIndex() < entity.getZIndex()) {
          entity.draw();
        }
      });
    }, this);
  }

  window.TileMap = TileMap;
})();
<commit_msg>Remove some unused code and allow base layers to omit properties field<commit_after>(function() {
  var TileMap = function(options) {
    this.canvas = options.canvas;
    this.context = this.canvas.getContext('2d');
    this.map = options.map;
    this.entities = options.entities || [],

    // Add each image to the appropriate tileset and patch a function to verify
    // it's index boundaries
    this.map.tilesets.forEach(function(tileset, index, tilesets) {
      var tiles = Math.floor(tileset.imagewidth / tileset.tilewidth) *
                  Math.floor(tileset.imageheight / tileset.tileheight);

      tilesets[index].image = options.images[index];
      tilesets[index].min = tileset.firstgid - 1;
      tilesets[index].max = tilesets[index].min + tiles;

      // Check whether the 'number' is in this tileset or not
      tilesets[index].inside = function(number) {
        return number >= this.min && this.max >= number;
      }.bind(tileset);
    });

    // Normalize layers to a zero indexed map
    var map = this.map;
    this.map.layers.forEach(function(layer, index, layers) {
      layers[index].properties = layer.properties || {};
      layer.data.forEach(function(number, index, layer) {
        layer[index] = number - 1;
      });
      layers[index].getZIndex = function() {
        var top = parseFloat(this.properties.zIndex || -999) * map.tileheight;
        return top + map.tileheight;
      }.bind(layers[index]);
    });
  }

  TileMap.prototype.clip = function(index) {
    var tileset = null;
    this.map.tilesets.forEach(function(set) {
      if (set.inside(index)) {
        tileset = set;
      }
    });
    var row = Math.floor((index * tileset.tilewidth) / tileset.imagewidth);

    return {
      image: tileset.image,
      x: (index * tileset.tilewidth) % tileset.imagewidth,
      y: (row * tileset.tileheight) % tileset.imageheight,
      width: tileset.tilewidth,
      height: tileset.tileheight,
    }
  }

  TileMap.prototype.draw = function(offsetX, offsetY) {
    var pendingEntities = this.entities.slice(0);
    this.map.layers.forEach(function(layer) {
      layer.data.forEach(function(number, index) {
        if (number < 0) {
          return;
        }

        var x = (index % layer.width);
        var y = Math.floor(index / layer.width);
        var clip = this.clip(number);
        this.context.drawImage(clip.image, clip.x, clip.y, clip.width, clip.height,
                               (x * Map.tilewidth) + offsetX, (y * Map.tileheight) + offsetY,
                               Map.tilewidth, Map.tileheight);
      }, this);
      pendingEntities.forEach(function(entity, index, pending) {
        if (layer.getZIndex() < entity.getZIndex()) {
          entity.draw();
        }
      });
    }, this);
  }

  window.TileMap = TileMap;
})();
<|endoftext|>


<commit_before>function getCollections() {
    var collections = [];
    var node = $('#tree').treeview('getNode', 0)
    collections.push(node);
    $.merge(collections, $('#tree').treeview('getSiblings', node));
    return collections;
}

function getSelectedCollection() {
    var nodeId;
    var selectedCollection = $('#tree').treeview('getSelected', nodeId)[0];
    return selectedCollection;
}

function getSelectedBookmarkCard(card) {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = getIndexCard(card);
    return selectedCollection.bookmarks[bookmarkIndex];
}

function getSelectedBookmark(node) {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = getIndex(node);
    return selectedCollection.bookmarks[bookmarkIndex];
}

function getIndex(node) {
    return $($(node).closest('li')[0]).index();
}

function getIndexCard(node) {
    return $($(node).closest('.card')[0]).index();
}

function syncTree(callback) {
    var selectedCollection = getSelectedCollection();

    loadFromFirebase(function(data){
        parse(data);
        $('#tree').treeview('selectNode', selectedCollection.nodeId)
        callback();
    });
}

function updateTree(element, replaceCollections) {
    if (replaceCollections) {
        saveToFirebase(element);
        parse(element);
    } else {
        var nodes = [];

        if ($('#tree').treeview('getEnabled').length > 0 && $('#tree').treeview('getEnabled')[0].id !== "tree") {
            nodes.push($('#tree').treeview('getNode', 0));
            $.merge(nodes, $('#tree').treeview('getSiblings', nodes[0]));
        }

        if (typeof element != 'undefined') {
            nodes.push(element);
        }

        saveToFirebase(nodes);

        parse(nodes);
    }

    updateSelectedCollection();
}

function updateSelectedCollection() {
    var selectedCollection = getSelectedCollection();
    if (typeof selectedCollection != 'undefined') {
        $('#tree').treeview('unselectNode', selectedCollection);
        $('#tree').treeview('selectNode', selectedCollection);
    }
}

Array.prototype.move = function (old_index, new_index) {
    if (new_index >= this.length) {
        var k = new_index - this.length;
        while ((k--) + 1) {
            this.push(undefined);
        }
    }
    this.splice(new_index, 0, this.splice(old_index, 1)[0]);
    return this; // for testing purposes
};

function loadMoveBookmarkTree() {
    loadFromFirebase(function(data) {
    $('#moveBookmark').treeview({
        data: data,
        collapseIcon: "fal fa-folder-open",
        expandIcon: "fal fa-folder",
        emptyIcon: "fal fa-folder"
    });
    $('#moveBookmark').treeview('collapseAll', { silent: true });
    $('#moveBookmark').on('nodeSelected', function(event, data) {
        $("#editMoveToCollection").text(data.nodeId);
        $("#editBookmarkCollection").val(data.text);
    });
  });
}

function parse(input) {
    $('#tree').treeview({
        data: input,
        collapseIcon: "fal fa-folder-open",
        expandIcon: "fal fa-folder",
        backColor: "rgb(246, 246, 246)",
        emptyIcon: "fal fa-folder",
        showTags: localStorage.getItem("showBookmarkCount") == "true"
    });

    $('#tree').on('nodeSelected', function(event, data) {
        $("#accordion").empty();
        $('#bookmarks').empty();
        $('#bookmarksCards').empty();

        if (data.showBookmarksAsCards) {
            if (typeof data.bookmarks != 'undefined' && data.bookmarks.length > 0) {
                $("#bookmarksCards").append(showBookmarks(data, true));
                $('[data-toggle="tooltip"]').tooltip();
            }
        } else {
            if (typeof data.bookmarks != 'undefined' && data.bookmarks.length > 0) {
                $("#bookmarks").append(showBookmarks(data));
                $("#bookmarkColumn").css("background-image", "linear-gradient(rgba(255,255,255,0.5),rgba(255,255,255,0.5)), url(" + getCollectionBackground(data) + ")")
            }
        }

        if (typeof data.nodes != 'undefined' && data.nodes.length > 0) {
            $.each(data.nodes, function (index, node) {
                var card = "
";

                card += "
";
                card += "
";
                card += "";
                card += node.text;
                card += "";
                card += "
"
                card += "
";

                card += "
";

                card += "
"
                card += showBookmarks(node, "#nodeBookmarks" + index);
                card += "
";

                card += "

";

                $("#accordion").append(card);
            });
        }

        $("#search").attr("placeholder", data.text);

        // Set the collection icon in the search bar
        if (typeof data.icon != 'undefined' && data.icon != "") {
            $("#collectionIcon").html("");
        } else {
            $("#collectionIcon").html("");
        }

        // Enable Copy Url buttons
        var clipboard = new Clipboard('.btn-clipboard');
        clipboard.on('success', function(e) {
            $.notify(
                {
                    message: "Bookmark URL copied to clipboard"
                }
                ,{
                    type: 'success',
                    delay: 1000
                }
            );
        });

        // Make the bookmarks sortable
        var el = document.getElementById('bookmarks');
        if (el.children.length > 0) {
            var sortable = Sortable.create(el, {
                onSort: function (evt) {
                    var collection = getSelectedCollection();
                    collection.bookmarks.move(evt.oldIndex, evt.newIndex);
                    updateTree();
                }
            });
        }

        // Make the bookmark cards sortable
        var el = document.getElementById('bookmarksCards');
        if (el.children.length > 0) {
            var sortable = Sortable.create(el, {
                onSort: function (evt) {
                    var collection = getSelectedCollection();
                    collection.bookmarks.move(evt.oldIndex, evt.newIndex);
                    updateTree();
                }
            });
        }

        // Make the collections sortable
        var el = document.getElementById('tree').children[0];
        if (el.children.length > 0) {
            var sortable = Sortable.create(el, {
                onEnd: function (evt) {
                    var collection = getSelectedCollection();
                    var parent = $('#tree').treeview('getParent', collection);

                    if (typeof parent == 'undefined') {
                        var collections = getCollections();

                        var temp = collections[evt.oldIndex];
                        collections[evt.oldIndex] = collections[evt.newIndex];
                        collections[evt.newIndex] = temp;

                        updateTree(collections, true);
                    } else {
                        parent.nodes.move(evt.oldIndex - 1, evt.newIndex - 1);
                        updateTree();
                    }
                }
            });
        }
    });
}<commit_msg>Fix multiple notifications on Copy Url button<commit_after>function getCollections() {
    var collections = [];
    var node = $('#tree').treeview('getNode', 0)
    collections.push(node);
    $.merge(collections, $('#tree').treeview('getSiblings', node));
    return collections;
}

function getSelectedCollection() {
    var nodeId;
    var selectedCollection = $('#tree').treeview('getSelected', nodeId)[0];
    return selectedCollection;
}

function getSelectedBookmarkCard(card) {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = getIndexCard(card);
    return selectedCollection.bookmarks[bookmarkIndex];
}

function getSelectedBookmark(node) {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = getIndex(node);
    return selectedCollection.bookmarks[bookmarkIndex];
}

function getIndex(node) {
    return $($(node).closest('li')[0]).index();
}

function getIndexCard(node) {
    return $($(node).closest('.card')[0]).index();
}

function syncTree(callback) {
    var selectedCollection = getSelectedCollection();

    loadFromFirebase(function(data){
        parse(data);
        $('#tree').treeview('selectNode', selectedCollection.nodeId)
        callback();
    });
}

function updateTree(element, replaceCollections) {
    if (replaceCollections) {
        saveToFirebase(element);
        parse(element);
    } else {
        var nodes = [];

        if ($('#tree').treeview('getEnabled').length > 0 && $('#tree').treeview('getEnabled')[0].id !== "tree") {
            nodes.push($('#tree').treeview('getNode', 0));
            $.merge(nodes, $('#tree').treeview('getSiblings', nodes[0]));
        }

        if (typeof element != 'undefined') {
            nodes.push(element);
        }

        saveToFirebase(nodes);

        parse(nodes);
    }

    updateSelectedCollection();
}

function updateSelectedCollection() {
    var selectedCollection = getSelectedCollection();
    if (typeof selectedCollection != 'undefined') {
        $('#tree').treeview('unselectNode', selectedCollection);
        $('#tree').treeview('selectNode', selectedCollection);
    }
}

Array.prototype.move = function (old_index, new_index) {
    if (new_index >= this.length) {
        var k = new_index - this.length;
        while ((k--) + 1) {
            this.push(undefined);
        }
    }
    this.splice(new_index, 0, this.splice(old_index, 1)[0]);
    return this; // for testing purposes
};

function loadMoveBookmarkTree() {
    loadFromFirebase(function(data) {
    $('#moveBookmark').treeview({
        data: data,
        collapseIcon: "fal fa-folder-open",
        expandIcon: "fal fa-folder",
        emptyIcon: "fal fa-folder"
    });
    $('#moveBookmark').treeview('collapseAll', { silent: true });
    $('#moveBookmark').on('nodeSelected', function(event, data) {
        $("#editMoveToCollection").text(data.nodeId);
        $("#editBookmarkCollection").val(data.text);
    });
  });
}

function parse(input) {
    $('#tree').treeview({
        data: input,
        collapseIcon: "fal fa-folder-open",
        expandIcon: "fal fa-folder",
        backColor: "rgb(246, 246, 246)",
        emptyIcon: "fal fa-folder",
        showTags: localStorage.getItem("showBookmarkCount") == "true"
    });

    var clipboard;

    $('#tree').on('nodeSelected', function(event, data) {
        $("#accordion").empty();
        $('#bookmarks').empty();
        $('#bookmarksCards').empty();

        if (data.showBookmarksAsCards) {
            if (typeof data.bookmarks != 'undefined' && data.bookmarks.length > 0) {
                $("#bookmarksCards").append(showBookmarks(data, true));
                $('[data-toggle="tooltip"]').tooltip();
            }
        } else {
            if (typeof data.bookmarks != 'undefined' && data.bookmarks.length > 0) {
                $("#bookmarks").append(showBookmarks(data));
                $("#bookmarkColumn").css("background-image", "linear-gradient(rgba(255,255,255,0.5),rgba(255,255,255,0.5)), url(" + getCollectionBackground(data) + ")")
            }
        }

        if (typeof data.nodes != 'undefined' && data.nodes.length > 0) {
            $.each(data.nodes, function (index, node) {
                var card = "
";

                card += "
";
                card += "
";
                card += "";
                card += node.text;
                card += "";
                card += "
"
                card += "
";

                card += "
";

                card += "
"
                card += showBookmarks(node, "#nodeBookmarks" + index);
                card += "
";

                card += "

";

                $("#accordion").append(card);
            });
        }

        $("#search").attr("placeholder", data.text);

        // Set the collection icon in the search bar
        if (typeof data.icon != 'undefined' && data.icon != "") {
            $("#collectionIcon").html("");
        } else {
            $("#collectionIcon").html("");
        }

        // Enable Copy Url buttons
        if (clipboard) {
            clipboard.destroy();
        }

        clipboard = new Clipboard('.btn-clipboard');
        clipboard.on('success', function(e) {
            $.notify(
                {
                    message: "Bookmark URL copied to clipboard"
                }
                ,{
                    type: 'success',
                    delay: 1000
                }
            );
        });

        // Make the bookmarks sortable
        var el = document.getElementById('bookmarks');
        if (el.children.length > 0) {
            var sortable = Sortable.create(el, {
                onSort: function (evt) {
                    var collection = getSelectedCollection();
                    collection.bookmarks.move(evt.oldIndex, evt.newIndex);
                    updateTree();
                }
            });
        }

        // Make the bookmark cards sortable
        var el = document.getElementById('bookmarksCards');
        if (el.children.length > 0) {
            var sortable = Sortable.create(el, {
                onSort: function (evt) {
                    var collection = getSelectedCollection();
                    collection.bookmarks.move(evt.oldIndex, evt.newIndex);
                    updateTree();
                }
            });
        }

        // Make the collections sortable
        var el = document.getElementById('tree').children[0];
        if (el.children.length > 0) {
            var sortable = Sortable.create(el, {
                onEnd: function (evt) {
                    var collection = getSelectedCollection();
                    var parent = $('#tree').treeview('getParent', collection);

                    if (typeof parent == 'undefined') {
                        var collections = getCollections();

                        var temp = collections[evt.oldIndex];
                        collections[evt.oldIndex] = collections[evt.newIndex];
                        collections[evt.newIndex] = temp;

                        updateTree(collections, true);
                    } else {
                        parent.nodes.move(evt.oldIndex - 1, evt.newIndex - 1);
                        updateTree();
                    }
                }
            });
        }
    });
}<|endoftext|>


<commit_before>import fetch from 'unfetch';
// eslint-disable-next-line import/no-extraneous-dependencies
import deepFreeze from 'deep-freeze'; // This is a devDependency because it is bundled in.
import ServerInterface from './server-interface';
import PromiseQueue from './promise-queue';

/**
 * Check if the target experiment is loaded on the server.
 * @param  {Object}  serverInterface the server interface.
 * @param  {string}  experimentId    the id of the target experiment.
 * @return {Promise}                 true if the experiment is loaded on the server.
 */
export async function isExperimentLoadedOnServer(
  serverInterface,
  experimentId
) {
  const experiments = await serverInterface.experiments();
  return !!experiments[experimentId];
}

/**
 * Fetch the given experiment design xml file and post it to be imported on the server.
 * @param  {Object}  serverInterface      the server interface.
 * @param  {string}  experimentDesignAddr the address where to downlad the experiment design.
 * @return {Promise}                      resolves when the experiment has been imported.
 */
export async function importExperimentOnServer(
  serverInterface,
  experimentDesignAddr
) {
  const designReq = await fetch(experimentDesignAddr);
  const design = await designReq.text();
  return serverInterface.postExperimentDesign(design);
}

/**
 * Select a run from the server.
 * @param  {Object}  serverInterface
 * @param  {string}  experimentId
 * @param  {string}  [runId]
 * @return {Promise}
 */
export async function selectRun(serverInterface, experimentId, runId) {
  const runInfo = runId
    ? await serverInterface.run(experimentId, runId)
    : await serverInterface.availableRun(experimentId);
  // Check if the ids are consistant.
  if (runInfo.experimentId !== experimentId) {
    throw new Error(
      'Received experiment id is inconsistant with the one that has been requested.'
    );
  } else if (runId && runInfo.id !== runId) {
    throw new Error(
      'Received run id is inconsistant with the one that has been requested.'
    );
  }
  return runInfo;
}

/**
 * Connect to a run (and lock it on the server)
 * @param  {Object}  serverInterface The server interface.
 * @param  {Object}  runInfo         The description of the target run.
 * @return {Promise<{id, experimentId, blocks, currentTrial, lock}>} The run information.
 */
export async function connectToRun(serverInterface, runInfo) {
  // Lock it, fetch the run plan and the current trial (current trial might not be the first
  // on if the run is being resumed).
  const [lock, blocksInfo, currentTrialInfo] = await Promise.all([
    serverInterface.lock(runInfo.experimentId, runInfo.id),
    serverInterface.plan(runInfo.experimentId, runInfo.id),
    serverInterface.currentTrial(runInfo.experimentId, runInfo.id)
  ]);

  return {
    id: runInfo.id,
    experimentId: runInfo.experimentId,
    blocks: blocksInfo,
    currentTrial: currentTrialInfo,
    lock
  };
}

/**
 * Consolidate a run by appending back references toward block on trials, and back references
 * toward the run on blocks
 * @param  {{id, experimentId, blocks}} runInfo The description of the run.
 * @return {{id, experimentId, blocks}}         The run consolidated and frozen.
 */
export function consolidateRun(runInfo) {
  const run = {
    id: runInfo.id,
    experimentId: runInfo.experimentId
  };
  // Insert backrefs to the parent block on each trials, and backrefs to the run on each
  // blocks.
  run.blocks = runInfo.blocks.map(block => {
    const newBlock = Object.assign({}, block, { run });
    newBlock.trials = block.trials.map(trial =>
      Object.assign({}, trial, { block: newBlock })
    );
    return newBlock;
  });
  deepFreeze(run);
  return run;
}

/**
 * RunConnection class.
 * @param  {string} run
 * @param  {string} token
 * @param  {int} startTrialNum
 * @param  {int} startBlockNum
 * @param  {PromiseQueue} postQueue
 * @param  {function} postTrial
 * @return {RunConnection}
 */
export default function RunConnection(
  run,
  token,
  startBlockNum,
  startTrialNum,
  postQueue,
  postTrial
) {
  // Maintain the current trial.
  let currentTrial = run.blocks[startBlockNum].trials[startTrialNum];
  // Register the promise of the last trial result post request. Used to make sure a post request
  // is done before sending a new one.
  let lastTrialResultPost;
  // Create the connection object.

  /**
   * Resolves with the connected run.
   * @return {Promise<Object>}
   */
  this.getRun = async () => run;

  /**
   * Resolves with the current block.
   * @return {Promise<Object>}
   */
  this.getCurrentBlock = async () => currentTrial && currentTrial.block;

  /**
   * Resolves with the current trial.
   * @return {Promise}
   */
  this.getCurrentTrial = async () => currentTrial;

  /**
   * Resolves with the next trial.
   * @return {Promise}
   */
  this.getNextTrial = async () => {
    let nextTrialNum = currentTrial.number + 1;
    let currentBlock = currentTrial.block;
    if (nextTrialNum >= currentBlock.trials.length) {
      currentBlock = run.blocks[currentBlock.number + 1];
      nextTrialNum = 0;
    }
    if (!currentBlock) {
      return undefined;
    }
    return currentBlock.trials[nextTrialNum];
  };

  /**
   * Disconnect (does nothing).
   * @return {Promise}
   */
  this.disconnect = async () => {
    // Nothing to do here.
  };

  /**
   * End a trial by posting its result to the server.
   * @param  {Object}  measures   The trial result.
   * @return {Promise}            A promise resolved when the trial results have been successfuly
   *                              pushed to the server
   */
  this.endCurrentTrial = async measures => {
    const previousTrial = await this.getCurrentTrial();
    if (!previousTrial) {
      throw new Error(
        'Cannot end current trial: it is unknown. ' +
          ' It might be because the run is not connected or it is already finished.'
      );
    }
    // Update the current trial.
    currentTrial = await this.getNextTrial();
    // Post the results.
    lastTrialResultPost = Promise.resolve(lastTrialResultPost).then(() =>
      postTrial(
        run.experimentId,
        run.id,
        previousTrial.block.number,
        previousTrial.number,
        { token, measures }
      )
    );
    // Push the post promise in the queue to monitor unfinished posts.
    postQueue.push(lastTrialResultPost);
    return currentTrial;
  };

  /**
   * Flush the trial result post queue.
   * @param  {int} maxLength  The maximum number of pending trial result post requests before
   *                          resolving the promise
   * @return {Promise}        A promise that resolves when there is maxLength or less pending
   *                          trial results. Resolves immediately if this is already the case
   *                          when the function is called.
   */
  this.flush = maxLength => postQueue.flush(maxLength);
}

/**
 * Create a connection to a run.
 * @param  {string|Object} serverAddressOrInterface The address of the server. Alternatively,
 *                                                  this can be provided as a server interface.
 * @param  {string}       experimentId The id of the experiment.
 * @param  {string}       [runId]  The id of the run.
 * @param  {string}       [experimentDesignAddr] The address of the experiment design (used to)
 *                                               import the experiment on the server if not already
 *                                               loaded.
 * @param  {PromiseQueue} [postQueue=new PromiseQueue()] Post queue that will monitor pending result
 *                                                       posts.
 * @return {Promise<Object>} The run connection.
 */
RunConnection.create = async function createRunConnection(
  serverAddressOrInterface,
  experimentId,
  runId,
  experimentDesignAddr,
  postQueue = new PromiseQueue()
) {
  // Create the interface to the server.
  const serverInterface = typeof serverAddress === 'string'
    ? new ServerInterface(serverAddressOrInterface)
    : serverAddressOrInterface;

  // Check if the experiment is loaded on the server, and if not load it.
  if (await isExperimentLoadedOnServer(serverInterface, experimentId)) {
    await importExperimentOnServer(serverInterface, experimentId);
  }

  // Connect to the run.
  const runInfo = await connectToRun(
    serverInterface,
    await selectRun(serverInterface, experimentId, runId)
  );

  // Consolidate the run (insert backrefs to blocks in trials and run in blocks, and deep freeze
  // everything).
  const run = consolidateRun(runInfo);

  return new RunConnection(
    run,
    runInfo.lock.token,
    runInfo.currentTrial.blockNumber,
    runInfo.currentTrial.number,
    postQueue,
    serverInterface.postTrial.bind(serverInterface)
  );
};
<commit_msg>fix run connection<commit_after>import fetch from 'unfetch';
// eslint-disable-next-line import/no-extraneous-dependencies
import deepFreeze from 'deep-freeze'; // This is a devDependency because it is bundled in.
import ServerInterface from './server-interface';
import PromiseQueue from './promise-queue';

/**
 * Check if the target experiment is loaded on the server.
 * @param  {Object}  serverInterface the server interface.
 * @param  {string}  experimentId    the id of the target experiment.
 * @return {Promise}                 true if the experiment is loaded on the server.
 */
export async function isExperimentLoadedOnServer(
  serverInterface,
  experimentId
) {
  const experiments = await serverInterface.experiments();
  return !!experiments[experimentId];
}

/**
 * Fetch the given experiment design xml file and post it to be imported on the server.
 * @param  {Object}  serverInterface      the server interface.
 * @param  {string}  experimentDesignAddr the address where to downlad the experiment design.
 * @return {Promise}                      resolves when the experiment has been imported.
 */
export async function importExperimentOnServer(
  serverInterface,
  experimentDesignAddr
) {
  const designReq = await fetch(experimentDesignAddr);
  const design = await designReq.text();
  return serverInterface.postExperimentDesign(design);
}

/**
 * Select a run from the server.
 * @param  {Object}  serverInterface
 * @param  {string}  experimentId
 * @param  {string}  [runId]
 * @return {Promise}
 */
export async function selectRun(serverInterface, experimentId, runId) {
  const runInfo = runId
    ? await serverInterface.run(experimentId, runId)
    : await serverInterface.availableRun(experimentId);
  // Check if the ids are consistant.
  if (runInfo.experimentId !== experimentId) {
    throw new Error(
      'Received experiment id is inconsistant with the one that has been requested.'
    );
  } else if (runId && runInfo.id !== runId) {
    throw new Error(
      'Received run id is inconsistant with the one that has been requested.'
    );
  }
  return runInfo;
}

/**
 * Connect to a run (and lock it on the server)
 * @param  {Object}  serverInterface The server interface.
 * @param  {Object}  runInfo         The description of the target run.
 * @return {Promise<{id, experimentId, blocks, currentTrial, lock}>} The run information.
 */
export async function connectToRun(serverInterface, runInfo) {
  // Lock it, fetch the run plan and the current trial (current trial might not be the first
  // on if the run is being resumed).
  const [lock, blocksInfo, currentTrialInfo] = await Promise.all([
    serverInterface.lock(runInfo.experimentId, runInfo.id),
    serverInterface.plan(runInfo.experimentId, runInfo.id),
    serverInterface.currentTrial(runInfo.experimentId, runInfo.id)
  ]);

  return {
    id: runInfo.id,
    experimentId: runInfo.experimentId,
    blocks: blocksInfo,
    currentTrial: currentTrialInfo,
    lock
  };
}

/**
 * Consolidate a run by appending back references toward block on trials, and back references
 * toward the run on blocks
 * @param  {{id, experimentId, blocks}} runInfo The description of the run.
 * @return {{id, experimentId, blocks}}         The run consolidated and frozen.
 */
export function consolidateRun(runInfo) {
  const run = {
    id: runInfo.id,
    experimentId: runInfo.experimentId
  };
  // Insert backrefs to the parent block on each trials, and backrefs to the run on each
  // blocks.
  run.blocks = runInfo.blocks.map(block => {
    const newBlock = Object.assign({}, block, { run });
    newBlock.trials = block.trials.map(trial =>
      Object.assign({}, trial, { block: newBlock })
    );
    return newBlock;
  });
  deepFreeze(run);
  return run;
}

/**
 * RunConnection class.
 * @param  {string} run
 * @param  {string} token
 * @param  {int} startTrialNum
 * @param  {int} startBlockNum
 * @param  {PromiseQueue} postQueue
 * @param  {function} postTrial
 * @return {RunConnection}
 */
export default function RunConnection(
  run,
  token,
  startBlockNum,
  startTrialNum,
  postQueue,
  postTrial
) {
  // Maintain the current trial.
  let currentTrial = run.blocks[startBlockNum].trials[startTrialNum];
  // Register the promise of the last trial result post request. Used to make sure a post request
  // is done before sending a new one.
  let lastTrialResultPost;
  // Create the connection object.

  /**
   * Resolves with the connected run.
   * @return {Promise<Object>}
   */
  this.getRun = async () => run;

  /**
   * Resolves with the current block.
   * @return {Promise<Object>}
   */
  this.getCurrentBlock = async () => currentTrial && currentTrial.block;

  /**
   * Resolves with the current trial.
   * @return {Promise}
   */
  this.getCurrentTrial = async () => currentTrial;

  /**
   * Resolves with the next trial.
   * @return {Promise}
   */
  this.getNextTrial = async () => {
    let nextTrialNum = currentTrial.number + 1;
    let currentBlock = currentTrial.block;
    if (nextTrialNum >= currentBlock.trials.length) {
      currentBlock = run.blocks[currentBlock.number + 1];
      nextTrialNum = 0;
    }
    if (!currentBlock) {
      return undefined;
    }
    return currentBlock.trials[nextTrialNum];
  };

  /**
   * Disconnect (does nothing).
   * @return {Promise}
   */
  this.disconnect = async () => {
    // Nothing to do here.
  };

  /**
   * End a trial by posting its result to the server.
   * @param  {Object}  measures   The trial result.
   * @return {Promise}            A promise resolved when the trial results have been successfuly
   *                              pushed to the server
   */
  this.endCurrentTrial = async measures => {
    const previousTrial = await this.getCurrentTrial();
    if (!previousTrial) {
      throw new Error(
        'Cannot end current trial: it is unknown. ' +
          ' It might be because the run is not connected or it is already finished.'
      );
    }
    // Update the current trial.
    currentTrial = await this.getNextTrial();
    // Post the results.
    lastTrialResultPost = Promise.resolve(lastTrialResultPost).then(() =>
      postTrial(
        run.experimentId,
        run.id,
        previousTrial.block.number,
        previousTrial.number,
        { token, measures }
      )
    );
    // Push the post promise in the queue to monitor unfinished posts.
    postQueue.push(lastTrialResultPost);
    return currentTrial;
  };

  /**
   * Flush the trial result post queue.
   * @param  {int} maxLength  The maximum number of pending trial result post requests before
   *                          resolving the promise
   * @return {Promise}        A promise that resolves when there is maxLength or less pending
   *                          trial results. Resolves immediately if this is already the case
   *                          when the function is called.
   */
  this.flush = maxLength => postQueue.flush(maxLength);
}

/**
 * Create a connection to a run.
 * @param  {string|Object} serverAddressOrInterface The address of the server. Alternatively,
 *                                                  this can be provided as a server interface.
 * @param  {string}       experimentId The id of the experiment.
 * @param  {string}       [runId]  The id of the run.
 * @param  {string}       [experimentDesignAddr] The address of the experiment design (used to)
 *                                               import the experiment on the server if not already
 *                                               loaded.
 * @param  {PromiseQueue} [postQueue=new PromiseQueue()] Post queue that will monitor pending result
 *                                                       posts.
 * @return {Promise<Object>} The run connection.
 */
RunConnection.create = async function createRunConnection(
  serverAddressOrInterface,
  experimentId,
  runId,
  experimentDesignAddr,
  postQueue = new PromiseQueue()
) {
  // Create the interface to the server.
  const serverInterface = typeof serverAddressOrInterface === 'string'
    ? new ServerInterface(serverAddressOrInterface)
    : serverAddressOrInterface;

  // Check if the experiment is loaded on the server, and if not load it.
  if (await isExperimentLoadedOnServer(serverInterface, experimentId)) {
    await importExperimentOnServer(serverInterface, experimentId);
  }

  // Connect to the run.
  const runInfo = await connectToRun(
    serverInterface,
    await selectRun(serverInterface, experimentId, runId)
  );

  // Consolidate the run (insert backrefs to blocks in trials and run in blocks, and deep freeze
  // everything).
  const run = consolidateRun(runInfo);

  return new RunConnection(
    run,
    runInfo.lock.token,
    runInfo.currentTrial.blockNumber,
    runInfo.currentTrial.number,
    postQueue,
    serverInterface.postTrial.bind(serverInterface)
  );
};
<|endoftext|>


<commit_before>/**
 *
 * Worker script for browser and node
 *
 * @fileoverview Worker script for browser and node
 * @author Kevin Kwok <antimatter15@gmail.com>
 * @author Guillermo Webster <gui@mit.edu>
 * @author Jerome Wu <jeromewus@gmail.com>
 */
require('regenerator-runtime/runtime');
const fileType = require('file-type');
const axios = require('axios');
const isURL = require('is-url');
const dump = require('./utils/dump');
const isBrowser = require('../utils/getEnvironment')('type') === 'browser';
const setImage = require('./utils/setImage');
const defaultParams = require('./constants/defaultParams');
const { log, setLogging } = require('../utils/log');

/*
 * Tesseract Module returned by TesseractCore.
 */
let TessModule;
/*
 * TessearctBaseAPI instance
 */
let api = null;
let latestJob;
let adapter = {};
let params = defaultParams;

const load = ({ workerId, jobId, payload: { options: { corePath, logging } } }, res) => {
  setLogging(logging);
  if (!TessModule) {
    const Core = adapter.getCore(corePath, res);

    res.progress({ workerId, status: 'initializing tesseract', progress: 0 });

    Core({
      TesseractProgress(percent) {
        latestJob.progress({
          workerId,
          jobId,
          status: 'recognizing text',
          progress: Math.max(0, (percent - 30) / 70),
        });
      },
    }).then((tessModule) => {
      TessModule = tessModule;
      res.progress({ workerId, status: 'initialized tesseract', progress: 1 });
      res.resolve({ loaded: true });
    });
  } else {
    res.resolve({ loaded: true });
  }
};

const loadLanguage = async ({
  workerId,
  payload: {
    langs,
    options: {
      langPath,
      dataPath,
      cachePath,
      cacheMethod,
      gzip = true,
    },
  },
},
  res) => {
  const loadAndGunzipFile = async (_lang) => {
    const lang = typeof _lang === 'string' ? _lang : _lang.code;
    const readCache = ['refresh', 'none'].includes(cacheMethod)
      ? () => Promise.resolve()
      : adapter.readCache;
    let data = null;

    try {
      const _data = await readCache(`${cachePath || '.'}/${lang}.traineddata`);
      if (typeof _data !== 'undefined') {
        log(`[${workerId}]: Load ${lang}.traineddata from cache`);
        data = _data;
      } else {
        throw Error('Not found in cache');
      }
    } catch (e) {
      log(`[${workerId}]: Load ${lang}.traineddata from ${langPath}`);
      if (typeof _lang === 'string') {
        let path = null;

        if (isURL(langPath) || langPath.startsWith('chrome-extension://') || langPath.startsWith('file://')) { /** When langPath is an URL */
          path = langPath;
        }

        if (path !== null) {
          const { data: _data } = await axios.get(
            `${path}/${lang}.traineddata${gzip ? '.gz' : ''}`,
            { responseType: 'arraybuffer' },
          );
          data = _data;
        } else {
          data = await adapter.readCache(`${langPath}/${lang}.traineddata${gzip ? '.gz' : ''}`);
        }
      } else {
        data = _lang.data; // eslint-disable-line
      }
    }

    data = new Uint8Array(data);

    const type = fileType(data);
    if (typeof type !== 'undefined' && type.mime === 'application/gzip') {
      data = adapter.gunzip(data);
    }

    if (TessModule) {
      if (dataPath) {
        try {
          TessModule.FS.mkdir(dataPath);
        } catch (err) {
          res.reject(err.toString());
        }
      }
      TessModule.FS.writeFile(`${dataPath || '.'}/${lang}.traineddata`, data);
    }

    if (['write', 'refresh', undefined].includes(cacheMethod)) {
      await adapter.writeCache(`${cachePath || '.'}/${lang}.traineddata`, data);
    }

    return Promise.resolve(data);
  };

  res.progress({ workerId, status: 'loading language traineddata', progress: 0 });
  try {
    await Promise.all((typeof langs === 'string' ? langs.split('+') : langs).map(loadAndGunzipFile));
    res.progress({ workerId, status: 'loaded language traineddata', progress: 1 });
    res.resolve(langs);
  } catch (err) {
    if (isBrowser && err instanceof DOMException) {
      /*
       * For some reason google chrome throw DOMException in loadLang,
       * while other browser is OK, for now we ignore this exception
       * and hopefully to find the root cause one day.
       */
    } else {
      res.reject(err.toString());
    }
  }
};

const setParameters = ({ payload: { params: _params } }, res) => {
  Object.keys(_params)
    .filter(k => !k.startsWith('tessjs_'))
    .forEach((key) => {
      api.SetVariable(key, _params[key]);
    });
  params = { ...params, ..._params };

  if (typeof res !== 'undefined') {
    res.resolve(params);
  }
};

const initialize = ({
  workerId,
  payload: { langs: _langs, oem },
}, res) => {
  const langs = (typeof _langs === 'string')
    ? _langs
    : _langs.map(l => ((typeof l === 'string') ? l : l.data)).join('+');

  try {
    res.progress({
      workerId, status: 'initializing api', progress: 0,
    });
    if (api !== null) {
      api.End();
    }
    api = new TessModule.TessBaseAPI();
    api.Init(null, langs, oem);
    params = defaultParams;
    setParameters({ payload: { params } });
    res.progress({
      workerId, status: 'initialized api', progress: 1,
    });
    res.resolve();
  } catch (err) {
    res.reject(err.toString());
  }
};

const recognize = ({ payload: { image, options: { rectangles = [] } } }, res) => {
  try {
    const ptr = setImage(TessModule, api, image);
    rectangles.forEach(({
      left, top, width, height,
    }) => {
      api.SetRectangle(left, top, width, height);
    });
    api.Recognize(null);
    res.resolve(dump(TessModule, api, params));
    TessModule._free(ptr);
  } catch (err) {
    res.reject(err.toString());
  }
};

const getPDF = ({ payload: { title, textonly } }, res) => {
  const pdfRenderer = new TessModule.TessPDFRenderer('tesseract-ocr', '/', textonly);
  pdfRenderer.BeginDocument(title);
  pdfRenderer.AddImage(api);
  pdfRenderer.EndDocument();
  TessModule._free(pdfRenderer);

  res.resolve(TessModule.FS.readFile('/tesseract-ocr.pdf'));
};

const detect = ({ payload: { image } }, res) => {
  try {
    const ptr = setImage(TessModule, api, image);
    const results = new TessModule.OSResults();

    if (!api.DetectOS(results)) {
      api.End();
      TessModule._free(ptr);
      res.reject('Failed to detect OS');
    } else {
      const best = results.best_result;
      const oid = best.orientation_id;
      const sid = best.script_id;

      TessModule._free(ptr);

      res.resolve({
        tesseract_script_id: sid,
        script: results.unicharset.get_script_from_script_id(sid),
        script_confidence: best.sconfidence,
        orientation_degrees: [0, 270, 180, 90][oid],
        orientation_confidence: best.oconfidence,
      });
    }
  } catch (err) {
    res.reject(err.toString());
  }
};

const terminate = (_, res) => {
  try {
    if (api !== null) {
      api.End();
    }
    res.resolve({ terminated: true });
  } catch (err) {
    res.reject(err.toString());
  }
};

/**
 * dispatchHandlers
 *
 * @name dispatchHandlers
 * @function worker data handler
 * @access public
 * @param {object} data
 * @param {string} data.jobId - unique job id
 * @param {string} data.action - action of the job, only recognize and detect for now
 * @param {object} data.payload - data for the job
 * @param {function} send - trigger job to work
 */
exports.dispatchHandlers = (packet, send) => {
  const res = (status, data) => {
    send({
      ...packet,
      status,
      data,
    });
  };
  res.resolve = res.bind(this, 'resolve');
  res.reject = res.bind(this, 'reject');
  res.progress = res.bind(this, 'progress');

  latestJob = res;

  try {
    ({
      load,
      loadLanguage,
      initialize,
      setParameters,
      recognize,
      getPDF,
      detect,
      terminate,
    })[packet.action](packet, res);
  } catch (err) {
    /** Prepare exception to travel through postMessage */
    res.reject(err.toString());
  }
};

/**
 * setAdapter
 *
 * @name setAdapter
 * @function
 * @access public
 * @param {object} adapter - implementation of the worker, different in browser and node environment
 */
exports.setAdapter = (_adapter) => {
  adapter = _adapter;
};
<commit_msg>Enable Firefox extesions too<commit_after>/**
 *
 * Worker script for browser and node
 *
 * @fileoverview Worker script for browser and node
 * @author Kevin Kwok <antimatter15@gmail.com>
 * @author Guillermo Webster <gui@mit.edu>
 * @author Jerome Wu <jeromewus@gmail.com>
 */
require('regenerator-runtime/runtime');
const fileType = require('file-type');
const axios = require('axios');
const isURL = require('is-url');
const dump = require('./utils/dump');
const isBrowser = require('../utils/getEnvironment')('type') === 'browser';
const setImage = require('./utils/setImage');
const defaultParams = require('./constants/defaultParams');
const { log, setLogging } = require('../utils/log');

/*
 * Tesseract Module returned by TesseractCore.
 */
let TessModule;
/*
 * TessearctBaseAPI instance
 */
let api = null;
let latestJob;
let adapter = {};
let params = defaultParams;

const load = ({ workerId, jobId, payload: { options: { corePath, logging } } }, res) => {
  setLogging(logging);
  if (!TessModule) {
    const Core = adapter.getCore(corePath, res);

    res.progress({ workerId, status: 'initializing tesseract', progress: 0 });

    Core({
      TesseractProgress(percent) {
        latestJob.progress({
          workerId,
          jobId,
          status: 'recognizing text',
          progress: Math.max(0, (percent - 30) / 70),
        });
      },
    }).then((tessModule) => {
      TessModule = tessModule;
      res.progress({ workerId, status: 'initialized tesseract', progress: 1 });
      res.resolve({ loaded: true });
    });
  } else {
    res.resolve({ loaded: true });
  }
};

const loadLanguage = async ({
  workerId,
  payload: {
    langs,
    options: {
      langPath,
      dataPath,
      cachePath,
      cacheMethod,
      gzip = true,
    },
  },
},
  res) => {
  const loadAndGunzipFile = async (_lang) => {
    const lang = typeof _lang === 'string' ? _lang : _lang.code;
    const readCache = ['refresh', 'none'].includes(cacheMethod)
      ? () => Promise.resolve()
      : adapter.readCache;
    let data = null;

    try {
      const _data = await readCache(`${cachePath || '.'}/${lang}.traineddata`);
      if (typeof _data !== 'undefined') {
        log(`[${workerId}]: Load ${lang}.traineddata from cache`);
        data = _data;
      } else {
        throw Error('Not found in cache');
      }
    } catch (e) {
      log(`[${workerId}]: Load ${lang}.traineddata from ${langPath}`);
      if (typeof _lang === 'string') {
        let path = null;

        if (isURL(langPath) || langPath.startsWith('moz-extension://') || langPath.startsWith('chrome-extension://') || langPath.startsWith('file://')) { /** When langPath is an URL */
          path = langPath;
        }

        if (path !== null) {
          const { data: _data } = await axios.get(
            `${path}/${lang}.traineddata${gzip ? '.gz' : ''}`,
            { responseType: 'arraybuffer' },
          );
          data = _data;
        } else {
          data = await adapter.readCache(`${langPath}/${lang}.traineddata${gzip ? '.gz' : ''}`);
        }
      } else {
        data = _lang.data; // eslint-disable-line
      }
    }

    data = new Uint8Array(data);

    const type = fileType(data);
    if (typeof type !== 'undefined' && type.mime === 'application/gzip') {
      data = adapter.gunzip(data);
    }

    if (TessModule) {
      if (dataPath) {
        try {
          TessModule.FS.mkdir(dataPath);
        } catch (err) {
          res.reject(err.toString());
        }
      }
      TessModule.FS.writeFile(`${dataPath || '.'}/${lang}.traineddata`, data);
    }

    if (['write', 'refresh', undefined].includes(cacheMethod)) {
      await adapter.writeCache(`${cachePath || '.'}/${lang}.traineddata`, data);
    }

    return Promise.resolve(data);
  };

  res.progress({ workerId, status: 'loading language traineddata', progress: 0 });
  try {
    await Promise.all((typeof langs === 'string' ? langs.split('+') : langs).map(loadAndGunzipFile));
    res.progress({ workerId, status: 'loaded language traineddata', progress: 1 });
    res.resolve(langs);
  } catch (err) {
    if (isBrowser && err instanceof DOMException) {
      /*
       * For some reason google chrome throw DOMException in loadLang,
       * while other browser is OK, for now we ignore this exception
       * and hopefully to find the root cause one day.
       */
    } else {
      res.reject(err.toString());
    }
  }
};

const setParameters = ({ payload: { params: _params } }, res) => {
  Object.keys(_params)
    .filter(k => !k.startsWith('tessjs_'))
    .forEach((key) => {
      api.SetVariable(key, _params[key]);
    });
  params = { ...params, ..._params };

  if (typeof res !== 'undefined') {
    res.resolve(params);
  }
};

const initialize = ({
  workerId,
  payload: { langs: _langs, oem },
}, res) => {
  const langs = (typeof _langs === 'string')
    ? _langs
    : _langs.map(l => ((typeof l === 'string') ? l : l.data)).join('+');

  try {
    res.progress({
      workerId, status: 'initializing api', progress: 0,
    });
    if (api !== null) {
      api.End();
    }
    api = new TessModule.TessBaseAPI();
    api.Init(null, langs, oem);
    params = defaultParams;
    setParameters({ payload: { params } });
    res.progress({
      workerId, status: 'initialized api', progress: 1,
    });
    res.resolve();
  } catch (err) {
    res.reject(err.toString());
  }
};

const recognize = ({ payload: { image, options: { rectangles = [] } } }, res) => {
  try {
    const ptr = setImage(TessModule, api, image);
    rectangles.forEach(({
      left, top, width, height,
    }) => {
      api.SetRectangle(left, top, width, height);
    });
    api.Recognize(null);
    res.resolve(dump(TessModule, api, params));
    TessModule._free(ptr);
  } catch (err) {
    res.reject(err.toString());
  }
};

const getPDF = ({ payload: { title, textonly } }, res) => {
  const pdfRenderer = new TessModule.TessPDFRenderer('tesseract-ocr', '/', textonly);
  pdfRenderer.BeginDocument(title);
  pdfRenderer.AddImage(api);
  pdfRenderer.EndDocument();
  TessModule._free(pdfRenderer);

  res.resolve(TessModule.FS.readFile('/tesseract-ocr.pdf'));
};

const detect = ({ payload: { image } }, res) => {
  try {
    const ptr = setImage(TessModule, api, image);
    const results = new TessModule.OSResults();

    if (!api.DetectOS(results)) {
      api.End();
      TessModule._free(ptr);
      res.reject('Failed to detect OS');
    } else {
      const best = results.best_result;
      const oid = best.orientation_id;
      const sid = best.script_id;

      TessModule._free(ptr);

      res.resolve({
        tesseract_script_id: sid,
        script: results.unicharset.get_script_from_script_id(sid),
        script_confidence: best.sconfidence,
        orientation_degrees: [0, 270, 180, 90][oid],
        orientation_confidence: best.oconfidence,
      });
    }
  } catch (err) {
    res.reject(err.toString());
  }
};

const terminate = (_, res) => {
  try {
    if (api !== null) {
      api.End();
    }
    res.resolve({ terminated: true });
  } catch (err) {
    res.reject(err.toString());
  }
};

/**
 * dispatchHandlers
 *
 * @name dispatchHandlers
 * @function worker data handler
 * @access public
 * @param {object} data
 * @param {string} data.jobId - unique job id
 * @param {string} data.action - action of the job, only recognize and detect for now
 * @param {object} data.payload - data for the job
 * @param {function} send - trigger job to work
 */
exports.dispatchHandlers = (packet, send) => {
  const res = (status, data) => {
    send({
      ...packet,
      status,
      data,
    });
  };
  res.resolve = res.bind(this, 'resolve');
  res.reject = res.bind(this, 'reject');
  res.progress = res.bind(this, 'progress');

  latestJob = res;

  try {
    ({
      load,
      loadLanguage,
      initialize,
      setParameters,
      recognize,
      getPDF,
      detect,
      terminate,
    })[packet.action](packet, res);
  } catch (err) {
    /** Prepare exception to travel through postMessage */
    res.reject(err.toString());
  }
};

/**
 * setAdapter
 *
 * @name setAdapter
 * @function
 * @access public
 * @param {object} adapter - implementation of the worker, different in browser and node environment
 */
exports.setAdapter = (_adapter) => {
  adapter = _adapter;
};
<|endoftext|>


<commit_before>function nullConverter(url) {
	return url;
}

/**
 * TinyMCE initialisation template.
 * $ variables are replaced by string search & replace.  It's pretty crude.
 */
// Prevents "Error: 'tinyMCE' is undefined" error in IE7 on Newsletter Recipient import.
if((typeof tinyMCE != 'undefined')) {
	tinyMCE.init({
		mode : "none",
		language: "$Lang",
		width: "100%",
		auto_resize : false,
		theme : "advanced",
		content_css : "$ContentCSS",
		document_base_url: "$BaseURL",
		urlconverter_callback : "nullConverter",

		setupcontent_callback : "sapphiremce_setupcontent",
		cleanup_callback : "sapphiremce_cleanup",

		theme_advanced_layout_manager: "SimpleLayout",
		theme_advanced_toolbar_location : "top",
		theme_advanced_toolbar_align : "left",
		theme_advanced_toolbar_parent : "right",
		plugins : "template,contextmenu,table,emotions,paste,../../tinymce_ssbuttons,spellchecker",
		table_inline_editing : true,
		theme_advanced_buttons1 : "bold,italic,underline,strikethrough,separator,justifyleft,justifycenter,justifyright,justifyfull,styleselect,formatselect,separator,bullist,numlist,outdent,indent,hr,charmap",
		theme_advanced_buttons2 : "undo,redo,separator,cut,copy,paste,pastetext,pasteword,spellchecker,separator,ssimage,ssflash,sslink,unlink,anchor,separator,template,code,separator,search,replace,selectall,visualaid,separator,tablecontrols",
		theme_advanced_buttons3 : "",
		spellchecker_languages : "$SpellcheckLangs",

		template_templates : [
		    { title : "Three column", src : "assets/snippet.html", description : "A simple 3 column layout"},
		],

		safari_warning : false,
		relative_urls : true,
		verify_html : true,
		valid_elements : "+a[id|rel|rev|dir|tabindex|accesskey|type|name|href|target|title|class],-strong/-b[class],-em/-i[class],-strike[class],-u[class],#p[id|dir|class|align],-ol[class],-ul[class],-li[class],br,img[id|dir|longdesc|usemap|class|src|border|alt=|title|width|height|align],-sub[class],-sup[class],-blockquote[dir|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|dir|id|style],-tr[id|dir|class|rowspan|width|height|align|valign|bgcolor|background|bordercolor|style],tbody[id|class|style],thead[id|class|style],tfoot[id|class|style],-td[id|dir|class|colspan|rowspan|width|height|align|valign|scope|style],-th[id|dir|class|colspan|rowspan|width|height|align|valign|scope|style],caption[id|dir|class],-div[id|dir|class|align],-span[class|align],-pre[class|align],address[class|align],-h1[id|dir|class|align],-h2[id|dir|class|align],-h3[id|dir|class|align],-h4[id|dir|class|align],-h5[id|dir|class|align],-h6[id|dir|class|align],hr[class],dd[id|class|title|dir],dl[id|class|title|dir],dt[id|class|title|dir]",
		extended_valid_elements : "img[class|src|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name],script[src|type],object[width|height|type|data],param[value|name]"
	});
}

Behaviour.register({
    'textarea.htmleditor' : {
        initialize : function() {
            tinyMCE.execCommand("mceAddControl", true, this.id);
        }
    }
})<commit_msg>#2991 - Fixed change detection in mce fields<commit_after>function nullConverter(url) {
	return url;
}

/**
 * TinyMCE initialisation template.
 * $ variables are replaced by string search & replace.  It's pretty crude.
 */
// Prevents "Error: 'tinyMCE' is undefined" error in IE7 on Newsletter Recipient import.
if((typeof tinyMCE != 'undefined')) {
	tinyMCE.init({
		mode : "none",
		language: "$Lang",
		width: "100%",
		auto_resize : false,
		theme : "advanced",
		content_css : "$ContentCSS",
		document_base_url: "$BaseURL",
		urlconverter_callback : "nullConverter",

		setupcontent_callback : "sapphiremce_setupcontent",
		cleanup_callback : "sapphiremce_cleanup",

		theme_advanced_layout_manager: "SimpleLayout",
		theme_advanced_toolbar_location : "top",
		theme_advanced_toolbar_align : "left",
		theme_advanced_toolbar_parent : "right",
		plugins : "template,contextmenu,table,emotions,paste,../../tinymce_ssbuttons,spellchecker",
		table_inline_editing : true,
		theme_advanced_buttons1 : "bold,italic,underline,strikethrough,separator,justifyleft,justifycenter,justifyright,justifyfull,styleselect,formatselect,separator,bullist,numlist,outdent,indent,hr,charmap",
		theme_advanced_buttons2 : "undo,redo,separator,cut,copy,paste,pastetext,pasteword,spellchecker,separator,ssimage,ssflash,sslink,unlink,anchor,separator,template,code,separator,search,replace,selectall,visualaid,separator,tablecontrols",
		theme_advanced_buttons3 : "",
		spellchecker_languages : "$SpellcheckLangs",

		template_templates : [
		    { title : "Three column", src : "assets/snippet.html", description : "A simple 3 column layout"},
		],

		safari_warning : false,
		relative_urls : true,
		verify_html : true,
		valid_elements : "+a[id|rel|rev|dir|tabindex|accesskey|type|name|href|target|title|class],-strong/-b[class],-em/-i[class],-strike[class],-u[class],#p[id|dir|class|align],-ol[class],-ul[class],-li[class],br,img[id|dir|longdesc|usemap|class|src|border|alt=|title|width|height|align],-sub[class],-sup[class],-blockquote[dir|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|dir|id|style],-tr[id|dir|class|rowspan|width|height|align|valign|bgcolor|background|bordercolor|style],tbody[id|class|style],thead[id|class|style],tfoot[id|class|style],-td[id|dir|class|colspan|rowspan|width|height|align|valign|scope|style],-th[id|dir|class|colspan|rowspan|width|height|align|valign|scope|style],caption[id|dir|class],-div[id|dir|class|align],-span[class|align],-pre[class|align],address[class|align],-h1[id|dir|class|align],-h2[id|dir|class|align],-h3[id|dir|class|align],-h4[id|dir|class|align],-h5[id|dir|class|align],-h6[id|dir|class|align],hr[class],dd[id|class|title|dir],dl[id|class|title|dir],dt[id|class|title|dir]",
		extended_valid_elements : "img[class|src|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name],script[src|type],object[width|height|type|data],param[value|name]"
	});
}

Behaviour.register({
    'textarea.htmleditor' : {
        initialize : function() {
            tinyMCE.execCommand("mceAddControl", true, this.id);
            this.isChanged = function() {
                return tinyMCE.getInstanceById(this.id).isDirty();
            }
        }
    }
})<|endoftext|>


<commit_before>/**
 * Entity Capabilities (XEP-0115)
 *
 * Depends on disco plugin.
 *
 * See: http://xmpp.org/extensions/xep-0115.html
 *
 * Authors:
 *   - Michael Weibel <michael.weibel@gmail.com>
 *
 * Copyright:
 *   - Michael Weibel <michael.weibel@gmail.com>
 */

 Strophe.addConnectionPlugin('caps', {
	/** Constant: HASH
	 * Hash used
	 *
	 * Currently only sha-1 is supported.
	 */
	HASH: 'sha-1',
	/** Variable: node
	 * Client which is being used.
	 *
	 * Can be overwritten as soon as Strophe has been initialized.
	 */
	node: 'http://strophe.im/strophejs/',
	/** PrivateVariable: _ver
	 * Own generated version string
	 */
	_ver: '',
	/** PrivateVariable: _connection
	 * Strophe connection
	 */
	_connection: null,
	/** PrivateVariable: _knownCapabilities
	 * A hashtable containing version-strings and their capabilities, serialized
	 * as string.
	 *
	 * TODO: Maybe those caps shouldn't be serialized.
	 */
	_knownCapabilities: {},
	/** PrivateVariable: _jidVerIndex
	 * A hashtable containing jids and their versions for better lookup of capabilities.
	 */
	_jidVerIndex: {},

	/** Function: init
	 * Initialize plugin:
	 *   - Add caps namespace
	 *   - Add caps feature to disco plugin
	 *   - Add handler for caps stanzas
	 *
	 * Parameters:
	 *   (Strophe.Connection) conn - Strophe connection
	 */
	init: function(conn) {
		this._connection = conn;

		Strophe.addNamespace('CAPS', 'http://jabber.org/protocol/caps');

		if (!this._connection.disco) {
			throw "Caps plugin requires the disco plugin to be installed.";
		}

		this._connection.disco.addFeature(Strophe.NS.CAPS);
		this._connection.addHandler(this._delegateCapabilities.bind(this), Strophe.NS.CAPS);
	},

	/** Function: generateCapsAttrs
	 * Returns the attributes for generating the "c"-stanza containing the own version
	 *
	 * Returns:
	 *   (Object) - attributes
	 */
	generateCapsAttrs: function() {
		return {
			'xmlns': Strophe.NS.CAPS,
			'hash': this.HASH,
			'node': this.node,
			'ver': this.generateVer()
		};
	},

	/** Function: generateVer
	 * Returns the base64 encoded version string (encoded itself with sha1)
	 *
	 * Returns:
	 *   (String) - version
	 */
	generateVer: function() {
		if (this._ver !== "") {
			return this._ver;
		}

		var ver = "",
			identities = this._connection.disco._identities.sort(this._sortIdentities),
			identitiesLen = identities.length,
			features = this._connection.disco._features.sort(),
			featuresLen = features.length;
		for(var i = 0; i < identitiesLen; i++) {
			var curIdent = identities[i];
			ver += curIdent.category + "/" + curIdent.type + "/" + curIdent.lang + "/" + curIdent.name + "<";
		}
		for(var i = 0; i < featuresLen; i++) {
			ver += features[i] + '<';
		}

		this._ver = b64_sha1(ver);
		return this._ver;
	},

	/** Function: getCapabilitiesByJid
	 * Returns serialized capabilities of a jid (if available).
	 * Otherwise null.
	 *
	 * Parameters:
	 *   (String) jid - Jabber id
	 *
	 * Returns:
	 *   (String|null) - capabilities, serialized; or null when not available.
	 */
	getCapabilitiesByJid: function(jid) {
		if (this._jidVerIndex[jid]) {
			return this._knownCapabilities[this._jidVerIndex[jid]];
		}
		return null;
	},

	/** PrivateFunction: _delegateCapabilities
	 * Checks if the version has already been saved.
	 * If yes: do nothing.
	 * If no: Request capabilities
	 *
	 * Parameters:
	 *   (Strophe.Builder) stanza - Stanza
	 *
	 * Returns:
	 *   (Boolean)
	 */
	_delegateCapabilities: function(stanza) {
		var from = stanza.getAttribute('from'),
			c = stanza.querySelector('c'),
			ver = c.getAttribute('ver'),
			node = c.getAttribute('node');
		if (!this._knownCapabilities[ver]) {
			return this._requestCapabilities(from, node, ver);
		} else {
			this._jidVerIndex[from] = ver;
		}
		if (!this._jidVerIndex[from] || !this._jidVerIndex[from] !== ver) {
			this._jidVerIndex[from] = ver;
		}
		return true;
	},

	/** PrivateFunction: _requestCapabilities
	 * Requests capabilities from the one which sent the caps-info stanza.
	 * This is done using disco info.
	 *
	 * Additionally, it registers a handler for handling the reply.
	 *
	 * Parameters:
	 *   (String) to - Destination jid
	 *   (String) node - Node attribute of the caps-stanza
	 *   (String) ver - Version of the caps-stanza
	 *
	 * Returns:
	 *   (Boolean) - true
	 */
	_requestCapabilities: function(to, node, ver) {
		if (to !== this._connection.jid) {
			var id = this._connection.disco.info(to, node + '#' + ver);
			this._connection.addHandler(this._handleDiscoInfoReply.bind(this), Strophe.NS.DISCO_INFO, 'iq', 'result', id, to);
		}
		return true;
	},

	/** PrivateFunction: _handleDiscoInfoReply
	 * Parses the disco info reply and adds the version & it's capabilities to the _knownCapabilities variable.
	 * Additionally, it adds the jid & the version to the _jidVerIndex variable for a better lookup.
	 *
	 * Parameters:
	 *   (Strophe.Builder) stanza - Disco info stanza
	 *
	 * Returns:
	 *   (Boolean) - false, to automatically remove the handler.
	 */
	_handleDiscoInfoReply: function(stanza) {
		var query = stanza.querySelector('query'),
			node = query.getAttribute('node').split('#'),
			ver = node[1],
			from = stanza.getAttribute('from');
		if (!this._knownCapabilities[ver]) {
			var childNodes = query.childNodes,
				childNodesLen = childNodes.length;
			this._knownCapabilities[ver] = [];
			for(var i = 0; i < childNodesLen; i++) {
				var node = childNodes[i];
				this._knownCapabilities[ver].push({name: node.nodeName, attributes: node.attributes});
			}
			this._jidVerIndex[from] = ver;
		} else if (!this._jidVerIndex[from] || !this._jidVerIndex[from] !== ver) {
			this._jidVerIndex[from] = ver;
		}
		return false;
	},

	/** PrivateFunction: _sortIdentities
	 * Sorts two identities according the sorting requirements in XEP-0115.
	 *
	 * Parameters:
	 *   (Object) a - Identity a
	 *   (Object) b - Identity b
	 *
	 * Returns:
	 *   (Integer) - 1, 0 or -1; according to which one's greater.
	 */
	_sortIdentities: function(a, b) {
		if (a.category > b.category) {
			return 1;
		}
		if (a.category < b.category) {
			return -1;
		}
		if (a.type > b.type) {
			return 1;
		}
		if (a.type < b.type) {
			return -1;
		}
		if (a.lang > b.lang) {
			return 1;
		}
		if (a.lang < b.lang) {
			return -1;
		}
		return 0;
	}
 });
<commit_msg>Replace odd chars with space.<commit_after>/**
 * Entity Capabilities (XEP-0115)
 *
 * Depends on disco plugin.
 *
 * See: http://xmpp.org/extensions/xep-0115.html
 *
 * Authors:
 *   - Michael Weibel <michael.weibel@gmail.com>
 *
 * Copyright:
 *   - Michael Weibel <michael.weibel@gmail.com>
 */

 Strophe.addConnectionPlugin('caps', {
	/** Constant: HASH
	 * Hash used
	 *
	 * Currently only sha-1 is supported.
	 */
	HASH: 'sha-1',
	/** Variable: node
	 * Client which is being used.
	 *
	 * Can be overwritten as soon as Strophe has been initialized.
	 */
	node: 'http://strophe.im/strophejs/',
	/** PrivateVariable: _ver
	 * Own generated version string
	 */
	_ver: '',
	/** PrivateVariable: _connection
	 * Strophe connection
	 */
	_connection: null,
	/** PrivateVariable: _knownCapabilities
	 * A hashtable containing version-strings and their capabilities, serialized
	 * as string.
	 *
	 * TODO: Maybe those caps shouldn't be serialized.
	 */
	_knownCapabilities: {},
	/** PrivateVariable: _jidVerIndex
	 * A hashtable containing jids and their versions for better lookup of capabilities.
	 */
	_jidVerIndex: {},

	/** Function: init
	 * Initialize plugin:
	 *   - Add caps namespace
	 *   - Add caps feature to disco plugin
	 *   - Add handler for caps stanzas
	 *
	 * Parameters:
	 *   (Strophe.Connection) conn - Strophe connection
	 */
	init: function(conn) {
		this._connection = conn;

		Strophe.addNamespace('CAPS', 'http://jabber.org/protocol/caps');

		if (!this._connection.disco) {
			throw "Caps plugin requires the disco plugin to be installed.";
		}

		this._connection.disco.addFeature(Strophe.NS.CAPS);
		this._connection.addHandler(this._delegateCapabilities.bind(this), Strophe.NS.CAPS);
	},

	/** Function: generateCapsAttrs
	 * Returns the attributes for generating the "c"-stanza containing the own version
	 *
	 * Returns:
	 *   (Object) - attributes
	 */
	generateCapsAttrs: function() {
		return {
			'xmlns': Strophe.NS.CAPS,
			'hash': this.HASH,
			'node': this.node,
			'ver': this.generateVer()
		};
	},

	/** Function: generateVer
	 * Returns the base64 encoded version string (encoded itself with sha1)
	 *
	 * Returns:
	 *   (String) - version
	 */
	generateVer: function() {
		if (this._ver !== "") {
			return this._ver;
		}

		var ver = "",
			identities = this._connection.disco._identities.sort(this._sortIdentities),
			identitiesLen = identities.length,
			features = this._connection.disco._features.sort(),
			featuresLen = features.length;
		for(var i = 0; i < identitiesLen; i++) {
			var curIdent = identities[i];
			ver += curIdent.category + "/" + curIdent.type + "/" + curIdent.lang + "/" + curIdent.name + "<";
		}
		for(var i = 0; i < featuresLen; i++) {
			ver += features[i] + '<';
		}

		this._ver = b64_sha1(ver);
		return this._ver;
	},

	/** Function: getCapabilitiesByJid
	 * Returns serialized capabilities of a jid (if available).
	 * Otherwise null.
	 *
	 * Parameters:
	 *   (String) jid - Jabber id
	 *
	 * Returns:
	 *   (String|null) - capabilities, serialized; or null when not available.
	 */
	getCapabilitiesByJid: function(jid) {
		if (this._jidVerIndex[jid]) {
			return this._knownCapabilities[this._jidVerIndex[jid]];
		}
		return null;
	},

	/** PrivateFunction: _delegateCapabilities
	 * Checks if the version has already been saved.
	 * If yes: do nothing.
	 * If no: Request capabilities
	 *
	 * Parameters:
	 *   (Strophe.Builder) stanza - Stanza
	 *
	 * Returns:
	 *   (Boolean)
	 */
	_delegateCapabilities: function(stanza) {
		var from = stanza.getAttribute('from'),
			c = stanza.querySelector('c'),
			ver = c.getAttribute('ver'),
			node = c.getAttribute('node');
		if (!this._knownCapabilities[ver]) {
			return this._requestCapabilities(from, node, ver);
		} else {
			this._jidVerIndex[from] = ver;
		}
		if (!this._jidVerIndex[from] || !this._jidVerIndex[from] !== ver) {
			this._jidVerIndex[from] = ver;
		}
		return true;
	},

	/** PrivateFunction: _requestCapabilities
	 * Requests capabilities from the one which sent the caps-info stanza.
	 * This is done using disco info.
	 *
	 * Additionally, it registers a handler for handling the reply.
	 *
	 * Parameters:
	 *   (String) to - Destination jid
	 *   (String) node - Node attribute of the caps-stanza
	 *   (String) ver - Version of the caps-stanza
	 *
	 * Returns:
	 *   (Boolean) - true
	 */
	_requestCapabilities: function(to, node, ver) {
		if (to !== this._connection.jid) {
			var id = this._connection.disco.info(to, node + '#' + ver);
			this._connection.addHandler(this._handleDiscoInfoReply.bind(this), Strophe.NS.DISCO_INFO, 'iq', 'result', id, to);
		}
		return true;
	},

	/** PrivateFunction: _handleDiscoInfoReply
	 * Parses the disco info reply and adds the version & it's capabilities to the _knownCapabilities variable.
	 * Additionally, it adds the jid & the version to the _jidVerIndex variable for a better lookup.
	 *
	 * Parameters:
	 *   (Strophe.Builder) stanza - Disco info stanza
	 *
	 * Returns:
	 *   (Boolean) - false, to automatically remove the handler.
	 */
	_handleDiscoInfoReply: function(stanza) {
		var query = stanza.querySelector('query'),
			node = query.getAttribute('node').split('#'),
			ver = node[1],
			from = stanza.getAttribute('from');
		if (!this._knownCapabilities[ver]) {
			var childNodes = query.childNodes,
				childNodesLen = childNodes.length;
			this._knownCapabilities[ver] = [];
			for(var i = 0; i < childNodesLen; i++) {
				var node =childNodes[i];
				this._knownCapabilities[ver].push({name: node.nodeName, attributes: node.attributes});
			}
			this._jidVerIndex[from] = ver;
		} else if (!this._jidVerIndex[from] || !this._jidVerIndex[from] !== ver) {
			this._jidVerIndex[from] = ver;
		}
		return false;
	},

	/** PrivateFunction: _sortIdentities
	 * Sorts two identities according the sorting requirements in XEP-0115.
	 *
	 * Parameters:
	 *   (Object) a - Identity a
	 *   (Object) b - Identity b
	 *
	 * Returns:
	 *   (Integer) - 1, 0 or -1; according to which one's greater.
	 */
	_sortIdentities: function(a, b) {
		if (a.category > b.category) {
			return 1;
		}
		if (a.category < b.category) {
			return -1;
		}
		if (a.type > b.type) {
			return 1;
		}
		if (a.type < b.type) {
			return -1;
		}
		if (a.lang > b.lang) {
			return 1;
		}
		if (a.lang < b.lang) {
			return -1;
		}
		return 0;
	}
 });
<|endoftext|>


<commit_before>var assert = require('assert');

createCollWithData = function(data) {
  var Fibers = Npm.require('fibers');
  Fibers(function() {
    coll = new Meteor.SmartCollection('abc');
    for(var lc =0; lc<data.length; lc++) {
      coll.insert(data[lc]);
    }
    emit('return');
  }).run();
}

suite('Cursor - .observeChanges()', function() {
  test('listen for added event - just cursor results', function(done, server) {
    var data = [{_id: 1, a: 10}, {_id: 2, b: 30}];
    server.evalSync(createCollWithData, data);

    server.eval(function() {
      Fibers(function() {
        cursor = coll.find();
        cursor.observeChanges({
          added: function(id, item) {
            emit('added', id, item);
          }
        });
        emit('done');
      }).run();
    });

    var received = [];
    server.on('added', function(id, item) {
      item._id = id;
      received.push(item);
    });

    server.on('done', function() {
      assert.deepEqual(received, data);
      done();
    });
  });

  test('just cursor results - with fibers', function(done, server) {
    var data = [{_id: 1, a: 10}, {_id: 2, b: 30}];
    server.evalSync(createCollWithData, data);

    var received = server.evalSync(function() {
      var Fibers = Npm.require('fibers');
      Fibers(function() {
        cursor = coll.find();
        var received = [];
        cursor.observeChanges({
          added: function(id, item) {
            item._id = id;
            received.push(item);
          }
        });
        emit('return', received);
      }).run();
    });

    assert.deepEqual(received, data);
    done();
  });

  test('just cursor results - with a used cursor', function(done, server) {
    var data = [{_id: 1, a: 10}, {_id: 2, b: 30}];
    server.evalSync(createCollWithData, data);

    server.evalSync(function() {
      cursor = coll.find();
      cursor.fetch(function() {
        emit('return');
      })
    });

    var received = server.evalSync(function() {
      var Fibers = Npm.require('fibers');
      Fibers(function() {
        var received = [];
        cursor.observeChanges({
          added: function(id, item) {
            item._id = id;
            received.push(item);
          }
        });
        emit('return', received);
      }).run();
    });

    assert.deepEqual(received, data);
    done();
  });

  test('adding cursor to Invalidator', function(done, server) {
    var data = [{_id: 1, a: 10}, {_id: 2, b: 30}];
    server.evalSync(createCollWithData, data);

    var same = server.evalSync(function() {
      var Fibers = Npm.require('fibers');
      Fibers(function() {
        cursor = coll.find();
        cursor.observeChanges({});

        var same = coll.invalidator._cursors[0] == cursor;
        emit('return', same);
      }).run();
    });

    assert.ok(same);
    done();
  });

  test('stop obeserving', function(done, server) {
    var data = [{_id: 1, a: 10}, {_id: 2, b: 30}];
    server.evalSync(createCollWithData, data);

    var status = server.evalSync(function() {
      var Fibers = Npm.require('fibers');
      Fibers(function() {
        cursor = coll.find();
        var status = [];
        var handler = cursor.observeChanges({});
        status.push(coll.invalidator._cursors[0] == cursor);

        handler.stop();
        status.push(coll.invalidator._cursors[0] == cursor);
        emit('return', status);
      }).run();
    });

    assert.deepEqual(status, [true, false]);
    done();
  });

  test('observeChanges callbacks on a fiber - updated', function(done, server) {
    var data = [{_id: 1, a: 10}, {_id: 2, b: 30}];
    server.evalSync(createCollWithData, data);

    server.eval(function() {
      cursor = coll.find();
      cursor.observeChanges({
        changed: function(id, item) {
          var count = coll.find().count();
          emit('count', count);
        }
      });
    });

    server.on('count', function(count) {
      assert.equal(count, 2);
      done();
    })

    server.evalSync(function() {
      coll.update({_id: 1}, {$set: {a: 30}});
      emit('return');
    });

  });

  test('observeChanges callbacks on a fiber - removed', function(done, server) {
    var data = [{_id: 1, a: 10}, {_id: 2, b: 30}];
    server.evalSync(createCollWithData, data);

    server.eval(function() {
      cursor = coll.find();
      cursor.observeChanges({
        removed: function(id, item) {
          var count = coll.find().count();
          emit('count', count);
        }
      });
    });

    server.on('count', function(count) {
      assert.equal(count, 1);
      done();
    })

    server.evalSync(function() {
      coll.remove({_id: 1});
      emit('return');
    });

  });

  test('observeChanges callbacks on a fiber - added', function(done, server) {
    var data = [{_id: 1, a: 10}, {_id: 2, b: 30}];
    server.evalSync(createCollWithData, data);

    server.eval(function() {
      cursor = coll.find();
      cursor.observeChanges({
        added: function(id, item) {
          var count = coll.find().count();
          emit('count', count);
        }
      });
    });

    var results = [];
    server.on('count', function(count) {
      results.push(count);
    })

    server.evalSync(function() {
      coll.insert({_id: 10});
      emit('return');
    });

    setTimeout(function() {
      assert.equal(results[2], 3);
      done();
    }, 50);

  });

  suite('Helpers', function() {
    test('_added', function(done, server) {
      var result = server.evalSync(function() {
        var Fibers = Npm.require('fibers');
        Fibers(function() {
          coll = new Meteor.SmartCollection('ssd');
          var cursor = coll.find();
          cursor.observeChanges({
            added: function(id, doc) {
              doc._id = id;
              emit('return', [doc, cursor._idMap]);
            }
          });

          cursor._added({_id: 20, aa: 10});
        }).run();
      });

      assert.deepEqual(result, [{_id: 20, aa: 10}, {"20": true}]);
      done();
    });

    test('_removed', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var cursor = coll.find();
          cursor.observeChanges({
            removed: function(id) {
              emit('return', [id, cursor._idMap]);
            }
          });

          cursor._idMap['20'] = true;
          cursor._removed(20);
        }).run();
      });

      assert.deepEqual(result, [20, {"20": null}]);
      done();
    });

    test('_changed', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var cursor = coll.find();
          cursor.observeChanges({
            changed: function(id, fields) {
              emit('return', [id, fields, cursor._idMap]);
            }
          });

          cursor._idMap['20'] = true;
          cursor._changed(20, {kk: 20});
        }).run();
      });

      assert.deepEqual(result, [20, {kk: 20}, {"20": true}]);
      done();
    });

    test('_computeAndNotifyRemoved', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var removed = [];
          var cursor = coll.find();
          cursor.observeChanges({
            removed: function(id) {
              removed.push(id);
            }
          });

          cursor._idMap = {
            "20": true,
            "10": true,
            "5": true
          };
          cursor._computeAndNotifyRemoved(['10', '5']);
          emit('return', [removed, cursor._idMap]);
        }).run();
      });

      assert.deepEqual(result, [[20], {"10": true, "5": true, "20": null}]);
      done();
    });

    test('_added - stop obeserving', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var cursor = coll.find();
          var received;
          cursor.observeChanges({
            added: function(id, doc) {
              received = doc;
            }
          }, function(err, handler) {
            handler.stop();
            cursor._added({_id: 20, aa: 10});
            emit('return', [received, cursor._idMap]);
          });
        }).run();
      });

      assert.deepEqual(result, [null, {}]);
      done();
    });

    test('_removed - stop obeserving', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var cursor = coll.find();
          var result;
          cursor.observeChanges({
            removed: function(id) {
              result = id;
            }
          }, function(err, handler) {
            handler.stop();
            cursor._idMap['20'] = true;
            cursor._removed(20);
            emit('return', [result, cursor._idMap]);
          });
        }).run();
      });

      assert.deepEqual(result, [null, {"20": true}]);
      done();
    });

    test('_changed - stop obeserving', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var cursor = coll.find();
          var changed;
          cursor.observeChanges({
            changed: function(id, fields) {
              changed = id;
            }
          }, function(err, handler) {
            handler.stop();
            cursor._idMap['20'] = true;
            cursor._changed(20, {kk: 20});
            emit('return', [changed, cursor._idMap]);
          });
        }).run();
      });

      assert.deepEqual(result, [null, {"20": true}]);
      done();
    });

    test('_computeAndNotifyRemoved - stop obeserving', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var removed = [];
          var cursor = coll.find();
          cursor.observeChanges({
            removed: function(id) {
              removed.push(id);
            }
          }, function(err, handler) {
            handler.stop();
            cursor._idMap = {
              "20": true,
              "10": true,
              "5": true
            };
            cursor._computeAndNotifyRemoved(['10', '5']);
            emit('return', [removed, cursor._idMap]);
          });
        }).run();
      });

      assert.deepEqual(result, [[], {"10": true, "5": true, "20": true}]);
      done();
    });
  });
}); <commit_msg>removed some errored (not test fails) tests<commit_after>var assert = require('assert');

createCollWithData = function(data) {
  var Fibers = Npm.require('fibers');
  Fibers(function() {
    coll = new Meteor.SmartCollection('abc');
    for(var lc =0; lc<data.length; lc++) {
      coll.insert(data[lc]);
    }
    emit('return');
  }).run();
}

suite('Cursor - .observeChanges()', function() {
  test('listen for added event - just cursor results', function(done, server) {
    var data = [{_id: 1, a: 10}, {_id: 2, b: 30}];
    server.evalSync(createCollWithData, data);

    server.eval(function() {
      Fibers(function() {
        cursor = coll.find();
        cursor.observeChanges({
          added: function(id, item) {
            emit('added', id, item);
          }
        });
        emit('done');
      }).run();
    });

    var received = [];
    server.on('added', function(id, item) {
      item._id = id;
      received.push(item);
    });

    server.on('done', function() {
      assert.deepEqual(received, data);
      done();
    });
  });

  test('just cursor results - with fibers', function(done, server) {
    var data = [{_id: 1, a: 10}, {_id: 2, b: 30}];
    server.evalSync(createCollWithData, data);

    var received = server.evalSync(function() {
      var Fibers = Npm.require('fibers');
      Fibers(function() {
        cursor = coll.find();
        var received = [];
        cursor.observeChanges({
          added: function(id, item) {
            item._id = id;
            received.push(item);
          }
        });
        emit('return', received);
      }).run();
    });

    assert.deepEqual(received, data);
    done();
  });

  test('just cursor results - with a used cursor', function(done, server) {
    var data = [{_id: 1, a: 10}, {_id: 2, b: 30}];
    server.evalSync(createCollWithData, data);

    server.evalSync(function() {
      cursor = coll.find();
      cursor.fetch(function() {
        emit('return');
      })
    });

    var received = server.evalSync(function() {
      var Fibers = Npm.require('fibers');
      Fibers(function() {
        var received = [];
        cursor.observeChanges({
          added: function(id, item) {
            item._id = id;
            received.push(item);
          }
        });
        emit('return', received);
      }).run();
    });

    assert.deepEqual(received, data);
    done();
  });

  test('adding cursor to Invalidator', function(done, server) {
    var data = [{_id: 1, a: 10}, {_id: 2, b: 30}];
    server.evalSync(createCollWithData, data);

    var same = server.evalSync(function() {
      var Fibers = Npm.require('fibers');
      Fibers(function() {
        cursor = coll.find();
        cursor.observeChanges({});

        var same = coll.invalidator._cursors[0] == cursor;
        emit('return', same);
      }).run();
    });

    assert.ok(same);
    done();
  });

  test('stop obeserving', function(done, server) {
    var data = [{_id: 1, a: 10}, {_id: 2, b: 30}];
    server.evalSync(createCollWithData, data);

    var status = server.evalSync(function() {
      var Fibers = Npm.require('fibers');
      Fibers(function() {
        cursor = coll.find();
        var status = [];
        var handler = cursor.observeChanges({});
        status.push(coll.invalidator._cursors[0] == cursor);

        handler.stop();
        status.push(coll.invalidator._cursors[0] == cursor);
        emit('return', status);
      }).run();
    });

    assert.deepEqual(status, [true, false]);
    done();
  });

  suite('Helpers', function() {
    test('_added', function(done, server) {
      var result = server.evalSync(function() {
        var Fibers = Npm.require('fibers');
        Fibers(function() {
          coll = new Meteor.SmartCollection('ssd');
          var cursor = coll.find();
          cursor.observeChanges({
            added: function(id, doc) {
              doc._id = id;
              emit('return', [doc, cursor._idMap]);
            }
          });

          cursor._added({_id: 20, aa: 10});
        }).run();
      });

      assert.deepEqual(result, [{_id: 20, aa: 10}, {"20": true}]);
      done();
    });

    test('_removed', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var cursor = coll.find();
          cursor.observeChanges({
            removed: function(id) {
              emit('return', [id, cursor._idMap]);
            }
          });

          cursor._idMap['20'] = true;
          cursor._removed(20);
        }).run();
      });

      assert.deepEqual(result, [20, {"20": null}]);
      done();
    });

    test('_changed', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var cursor = coll.find();
          cursor.observeChanges({
            changed: function(id, fields) {
              emit('return', [id, fields, cursor._idMap]);
            }
          });

          cursor._idMap['20'] = true;
          cursor._changed(20, {kk: 20});
        }).run();
      });

      assert.deepEqual(result, [20, {kk: 20}, {"20": true}]);
      done();
    });

    test('_computeAndNotifyRemoved', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var removed = [];
          var cursor = coll.find();
          cursor.observeChanges({
            removed: function(id) {
              removed.push(id);
            }
          });

          cursor._idMap = {
            "20": true,
            "10": true,
            "5": true
          };
          cursor._computeAndNotifyRemoved(['10', '5']);
          emit('return', [removed, cursor._idMap]);
        }).run();
      });

      assert.deepEqual(result, [[20], {"10": true, "5": true, "20": null}]);
      done();
    });

    test('_added - stop obeserving', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var cursor = coll.find();
          var received;
          cursor.observeChanges({
            added: function(id, doc) {
              received = doc;
            }
          }, function(err, handler) {
            handler.stop();
            cursor._added({_id: 20, aa: 10});
            emit('return', [received, cursor._idMap]);
          });
        }).run();
      });

      assert.deepEqual(result, [null, {}]);
      done();
    });

    test('_removed - stop obeserving', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var cursor = coll.find();
          var result;
          cursor.observeChanges({
            removed: function(id) {
              result = id;
            }
          }, function(err, handler) {
            handler.stop();
            cursor._idMap['20'] = true;
            cursor._removed(20);
            emit('return', [result, cursor._idMap]);
          });
        }).run();
      });

      assert.deepEqual(result, [null, {"20": true}]);
      done();
    });

    test('_changed - stop obeserving', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var cursor = coll.find();
          var changed;
          cursor.observeChanges({
            changed: function(id, fields) {
              changed = id;
            }
          }, function(err, handler) {
            handler.stop();
            cursor._idMap['20'] = true;
            cursor._changed(20, {kk: 20});
            emit('return', [changed, cursor._idMap]);
          });
        }).run();
      });

      assert.deepEqual(result, [null, {"20": true}]);
      done();
    });

    test('_computeAndNotifyRemoved - stop obeserving', function(done, server) {
      var result = server.evalSync(function() {
        coll = new Meteor.SmartCollection('ssd');
        Fibers(function() {
          var removed = [];
          var cursor = coll.find();
          cursor.observeChanges({
            removed: function(id) {
              removed.push(id);
            }
          }, function(err, handler) {
            handler.stop();
            cursor._idMap = {
              "20": true,
              "10": true,
              "5": true
            };
            cursor._computeAndNotifyRemoved(['10', '5']);
            emit('return', [removed, cursor._idMap]);
          });
        }).run();
      });

      assert.deepEqual(result, [[], {"10": true, "5": true, "20": true}]);
      done();
    });
  });
}); <|endoftext|>