<commit_before>(function() {

var unsubscribe = {};

/**
 * Models collection
 * @namespace
 * @constructor
 * @augments ns.Model
 */
ns.ModelCollection = function() {};

no.inherit(ns.ModelCollection, ns.Model);

ns.ModelCollection.prototype.getData = function() {
    // TODO а точно это нужно? Можно ведь просто всегда взять элементы из collection.models.

    // это составная модель —
    // нужно склеить все данные
    // из моделей её состовляющих
    if (this.isValid()) {
        var jpathItems;

        if (this.info.split) {
            jpathItems = this.info.split.items;
        } else if (this.info.jpathItems) {
            jpathItems = this.info.jpathItems;
        } else {
            jpathItems = '.items';
        }

        // если нет поля data сделаем его
        if (!this.data) {
            this.data = {};
        }

        // делаем нужное поле в .data и делаем его пустым
        no.jpath.set(jpathItems, this.data, []);
        // ссылка куда вставлять данные моделей
        var items = no.jpath(jpathItems, this.data);

        // пишем новые
        this.models.forEach(function(model) {
            items.push( model.getData() );
        });
    }
    return this.data;
};

ns.ModelCollection.prototype._reset = function() {
    ns.Model.prototype._reset.apply(this, arguments);

    this.clear();
};

/**
 * Разбивает данные через jpath описанный в info.split
 * на составные модели
 */
ns.ModelCollection.prototype._beforeSetData = function(data) {
    this.clear();

    var splitInfo = this.info.split;
    if (splitInfo) {
        var items = no.jpath(splitInfo.items, data);
        var models = this._splitModels(items);
        this.insert(models);
    }

    // TODO может быть стоит удалять данные split-а?

    return data;
};

/**
 * Создает модели из разбитых данных
 *
 * @param { Array<Object>} items – массив данных для будущих подмоделей
 * @return { Array<ns.Model> } – массив полученных подмоделей
 */
ns.ModelCollection.prototype._splitModels = function(items) {
    var info = this.info.split;

    return items.map(function(item) {
        var params = {};
        for (var key in info.params) {
            params[key] = no.jpath(info.params[key], item);
        }

        // идентификатор подмодели берется из info.model_id
        // коллецкия может содержать модели только одного вида
        return ns.Model.get(info.model_id, params).setData(item);
    });
};

/**
 * Подписывает коллекию на события из подмоделей
 *
 * @param {ns.Model} model
 */
ns.ModelCollection.prototype._subscribeSplit = function(model) {
    var that = this;

    var onModelChanged = function(evt, jpath) { that.onItemChanged(evt, model, jpath); };
    var onModelTouched = function(evt) { that.onItemTouched(evt, model); };
    var onModelDestroyed = function(evt) { that.onItemDestroyed(evt, model); };

    model.on('ns-model-change', onModelChanged);
    model.on('ns-model-touched', onModelTouched);
    model.on('ns-model-destroyed', onModelDestroyed);

    unsubscribe[model.key] = function() {
        model.off('ns-model-change', onModelChanged);
        model.off('ns-model-touched', onModelTouched);
        model.off('ns-model-destroyed', onModelDestroyed);
    };
};

ns.ModelCollection.prototype.onItemChanged = function(evt, model, jpath) {
    // TODO тут можно триггерить много чего, но мы пока этого не делаем:
    // this.trigger('ns-model-changed.items[3].some.inner.prop'); // (ЭТОГО СЕЙЧАС НЕТ).

    this.trigger('ns-model-changed', { 'model': model, 'jpath': jpath });
};

ns.ModelCollection.prototype.onItemTouched = function() {
    // TODO почему не this.touch() ?
    this._version++;
};

ns.ModelCollection.prototype.onItemDestroyed = function(evt, model) {
    // При уничтожении вложенной модели коллекция выносит останки.
    this.remove(model);
};

/**
 * Returns data version (included items version).
 * @returns {number}
 */
ns.ModelCollection.prototype.getSelfVersion = function() {
    return this._versionSelf;
};

/**
 * Обновляет _version модели
 */
ns.ModelCollection.prototype.touch = function() {
    ns.Model.prototype.touch.apply(this, arguments);

    /**
     * _versionSelf показывает версию изменений внешней модели
     * в то время, как _version - последнее время изменения внешней или внутренней модели
     * @type {*}
     * @private
     */
    this._versionSelf = this._version;
};

/**
 * Удаляет подписку коллекции на подмодель
 * Обязательно при удалении подмодели из коллекции
 *
 * @param {ns.Model} model
 */
ns.ModelCollection.prototype._unsubscribeSplit = function(model) {
    if (model && unsubscribe[model.key]) {
        unsubscribe[model.key]();
    }
};

/**
 * Удаляет все подмодели из коллекции
 * С каждой модели происходит снятие подписки
 */
ns.ModelCollection.prototype.clear = function() {
    if (this.models) {
        var that = this;
        this.models.forEach(function(model) {
            that._unsubscribeSplit(model);
        });
        this.models = [];
    }
};

/**
 * Вставляет подмодели в коллекцию
 *
 * @param {Array <ns.Model> | ns.Model} models – одна или несколько подмоделей
 *                                                 для вставки
 * @param {Number} [index] – индекс позиции, на которую вставить подмодели
 *
 * @return {Boolean} – признак успешности вставки
 */
ns.ModelCollection.prototype.insert = function(models, index) {
    if (isNaN(index)) {
        index = this.models.length;
    }

    // Для удобства, одиночная модель оборачивается в массив.
    if (!(models instanceof Array)) {
        models = [models];
    }

    // Вставить можно только объявленную модель
    // и повторная вставка модели запрещена
    var insertion = models.filter(function(model) {
        return this.models.indexOf(model) === -1 && ns.Model.infoLite(model.id);
    }, this);

    insertion.forEach(function(model, i) {
        this.models.splice(index + i, 0, model);

        // не забудем подписаться на события
        this._subscribeSplit(model);
    }, this);

    // оповестим всех, что вставили подмодели
    if (insertion.length > 0) {
        // если вставка данных состоялась, считаем модель валидной
        this.status = this.STATUS.OK;

        this.trigger('ns-model-insert', insertion);
        return true;
    } else {
        return false;
    }
};

/**
 * Удаляет подмодель из коллекции
 *
 * @param {ns.Model | Number | Array<ns.Model | Number>} models – подмодели или индексы подмодели, которую надо удалить
 * @return {Boolean} – признак успешности удаления
 */
ns.ModelCollection.prototype.remove = function(models) {

    var modelsRemoved = [];

    if (!(models instanceof Array)) {
        models = [models];
    }

    models.forEach(function(modelOrIndex) {
        var index;

        if (isNaN(modelOrIndex)) {
            index = this.models.indexOf(modelOrIndex);
        } else {
            index = modelOrIndex;
        }

        if (index >= 0) {
            var model = this.models[index];

            // не забудем отписаться от событий подмодели
            this._unsubscribeSplit(model);
            modelsRemoved.push(model);
            this.models.splice(index, 1);
        }

    }.bind(this));

    if (modelsRemoved.length) {
        this.trigger('ns-model-remove', modelsRemoved);
        return true;
    }

    return false;
};

})();
<commit_msg>rename info to splitInfo to not confuse it with info<commit_after>(function() {

var unsubscribe = {};

/**
 * Models collection
 * @namespace
 * @constructor
 * @augments ns.Model
 */
ns.ModelCollection = function() {};

no.inherit(ns.ModelCollection, ns.Model);

ns.ModelCollection.prototype.getData = function() {
    // TODO а точно это нужно? Можно ведь просто всегда взять элементы из collection.models.

    // это составная модель —
    // нужно склеить все данные
    // из моделей её состовляющих
    if (this.isValid()) {
        var jpathItems;

        if (this.info.split) {
            jpathItems = this.info.split.items;
        } else if (this.info.jpathItems) {
            jpathItems = this.info.jpathItems;
        } else {
            jpathItems = '.items';
        }

        // если нет поля data сделаем его
        if (!this.data) {
            this.data = {};
        }

        // делаем нужное поле в .data и делаем его пустым
        no.jpath.set(jpathItems, this.data, []);
        // ссылка куда вставлять данные моделей
        var items = no.jpath(jpathItems, this.data);

        // пишем новые
        this.models.forEach(function(model) {
            items.push( model.getData() );
        });
    }
    return this.data;
};

ns.ModelCollection.prototype._reset = function() {
    ns.Model.prototype._reset.apply(this, arguments);

    this.clear();
};

/**
 * Разбивает данные через jpath описанный в info.split
 * на составные модели
 */
ns.ModelCollection.prototype._beforeSetData = function(data) {
    this.clear();

    var splitInfo = this.info.split;
    if (splitInfo) {
        var items = no.jpath(splitInfo.items, data);
        var models = this._splitModels(items);
        this.insert(models);
    }

    // TODO может быть стоит удалять данные split-а?

    return data;
};

/**
 * Создает модели из разбитых данных
 *
 * @param { Array<Object>} items – массив данных для будущих подмоделей
 * @return { Array<ns.Model> } – массив полученных подмоделей
 */
ns.ModelCollection.prototype._splitModels = function(items) {
    var splitInfo = this.info.split;

    return items.map(function(item) {
        var params = {};
        for (var key in splitInfo.params) {
            params[key] = no.jpath(splitInfo.params[key], item);
        }

        // идентификатор подмодели берется из info.model_id
        // коллецкия может содержать модели только одного вида
        return ns.Model.get(splitInfo.model_id, params).setData(item);
    });
};

/**
 * Подписывает коллекию на события из подмоделей
 *
 * @param {ns.Model} model
 */
ns.ModelCollection.prototype._subscribeSplit = function(model) {
    var that = this;

    var onModelChanged = function(evt, jpath) { that.onItemChanged(evt, model, jpath); };
    var onModelTouched = function(evt) { that.onItemTouched(evt, model); };
    var onModelDestroyed = function(evt) { that.onItemDestroyed(evt, model); };

    model.on('ns-model-change', onModelChanged);
    model.on('ns-model-touched', onModelTouched);
    model.on('ns-model-destroyed', onModelDestroyed);

    unsubscribe[model.key] = function() {
        model.off('ns-model-change', onModelChanged);
        model.off('ns-model-touched', onModelTouched);
        model.off('ns-model-destroyed', onModelDestroyed);
    };
};

ns.ModelCollection.prototype.onItemChanged = function(evt, model, jpath) {
    // TODO тут можно триггерить много чего, но мы пока этого не делаем:
    // this.trigger('ns-model-changed.items[3].some.inner.prop'); // (ЭТОГО СЕЙЧАС НЕТ).

    this.trigger('ns-model-changed', { 'model': model, 'jpath': jpath });
};

ns.ModelCollection.prototype.onItemTouched = function() {
    // TODO почему не this.touch() ?
    this._version++;
};

ns.ModelCollection.prototype.onItemDestroyed = function(evt, model) {
    // При уничтожении вложенной модели коллекция выносит останки.
    this.remove(model);
};

/**
 * Returns data version (included items version).
 * @returns {number}
 */
ns.ModelCollection.prototype.getSelfVersion = function() {
    return this._versionSelf;
};

/**
 * Обновляет _version модели
 */
ns.ModelCollection.prototype.touch = function() {
    ns.Model.prototype.touch.apply(this, arguments);

    /**
     * _versionSelf показывает версию изменений внешней модели
     * в то время, как _version - последнее время изменения внешней или внутренней модели
     * @type {*}
     * @private
     */
    this._versionSelf = this._version;
};

/**
 * Удаляет подписку коллекции на подмодель
 * Обязательно при удалении подмодели из коллекции
 *
 * @param {ns.Model} model
 */
ns.ModelCollection.prototype._unsubscribeSplit = function(model) {
    if (model && unsubscribe[model.key]) {
        unsubscribe[model.key]();
    }
};

/**
 * Удаляет все подмодели из коллекции
 * С каждой модели происходит снятие подписки
 */
ns.ModelCollection.prototype.clear = function() {
    if (this.models) {
        var that = this;
        this.models.forEach(function(model) {
            that._unsubscribeSplit(model);
        });
        this.models = [];
    }
};

/**
 * Вставляет подмодели в коллекцию
 *
 * @param {Array <ns.Model> | ns.Model} models – одна или несколько подмоделей
 *                                                 для вставки
 * @param {Number} [index] – индекс позиции, на которую вставить подмодели
 *
 * @return {Boolean} – признак успешности вставки
 */
ns.ModelCollection.prototype.insert = function(models, index) {
    if (isNaN(index)) {
        index = this.models.length;
    }

    // Для удобства, одиночная модель оборачивается в массив.
    if (!(models instanceof Array)) {
        models = [models];
    }

    // Вставить можно только объявленную модель
    // и повторная вставка модели запрещена
    var insertion = models.filter(function(model) {
        return this.models.indexOf(model) === -1 && ns.Model.infoLite(model.id);
    }, this);

    insertion.forEach(function(model, i) {
        this.models.splice(index + i, 0, model);

        // не забудем подписаться на события
        this._subscribeSplit(model);
    }, this);

    // оповестим всех, что вставили подмодели
    if (insertion.length > 0) {
        // если вставка данных состоялась, считаем модель валидной
        this.status = this.STATUS.OK;

        this.trigger('ns-model-insert', insertion);
        return true;
    } else {
        return false;
    }
};

/**
 * Удаляет подмодель из коллекции
 *
 * @param {ns.Model | Number | Array<ns.Model | Number>} models – подмодели или индексы подмодели, которую надо удалить
 * @return {Boolean} – признак успешности удаления
 */
ns.ModelCollection.prototype.remove = function(models) {

    var modelsRemoved = [];

    if (!(models instanceof Array)) {
        models = [models];
    }

    models.forEach(function(modelOrIndex) {
        var index;

        if (isNaN(modelOrIndex)) {
            index = this.models.indexOf(modelOrIndex);
        } else {
            index = modelOrIndex;
        }

        if (index >= 0) {
            var model = this.models[index];

            // не забудем отписаться от событий подмодели
            this._unsubscribeSplit(model);
            modelsRemoved.push(model);
            this.models.splice(index, 1);
        }

    }.bind(this));

    if (modelsRemoved.length) {
        this.trigger('ns-model-remove', modelsRemoved);
        return true;
    }

    return false;
};

})();
<|endoftext|>


<commit_before>// @flow
const Dockerode = require('simple-dockerode')

import type { $Request, $Response } from "express";

import { exec as execSync } from "child_process";
import jsesc from "jsesc";
import Logger from "../logger/logger.model";
import { promisify } from "util";
import { timeout, TimeoutError } from "promise-timeout";

const exec = promisify(execSync);

// Compile java code
// export function compile(req: $Request, res: $Response) {
//   const fileName = req.body.fileName;
//   let dirName = `users/${req.body.user._id}/`;
//   const dateTime = new Date();
//   dirName += dateTime.getMonth();
//   dirName += dateTime.getDate();
//   dirName += dateTime.getFullYear();
//
//   // make a directory for this user, if doesn't exist already
//   exec(
//     `mkdir -p ${dirName}`,
//     { timeout: 10000 }, // Process will time out if running for > 10 seconds.
//     (error, stdout, stderr) => {
//       if (error) {
//         return res.status(200).send(stderr);
//       }
//
//       const escapedCode = jsesc(req.body.code, {
//         wrap: true
//       });
//       exec(
//         `echo ${escapedCode} > ${dirName}/${fileName}`,
//         { timeout: 10000 }, // Process will time out if running for > 10 seconds.
//         (error, stdout, stderr) => {
//           if (error) {
//             return res.status(200).send(stderr);
//           }
//           compileJavaFile(`${dirName}/${fileName}`, dirName, res);
//         }
//       );
//     }
//   );
// }
//
// // run java code
// export function run(req: $Request, res: $Response) {
//   let dirName = `users/${req.body.user._id}/`;
//   const dateTime = new Date();
//   dirName += dateTime.getMonth();
//   dirName += dateTime.getDate();
//   dirName += dateTime.getFullYear();
//   const execFile = req.body.fileName.replace(".c", "");
//   const cmd = `"${dirName}/${execFile}"`;
//   exec(
//     cmd,
//     { timeout: 10000 }, // Process will time out if running for > 10 seconds.
//     (error, stdout, stderr) => {
//       if (error) {
//         return res.status(200).send(error);
//       }
//       return res.status(200).send({ stdout, stderr });
//     }
//   );
// }
//
// function compileJavaFile(srcFile, dirName, res) {
//   const execFile = srcFile.replace(".c", "");
//   // exec is asynchronous
//   exec(
//     `gcc "${srcFile}" -o "${execFile}"`,
//     { timeout: 10000 }, // Process will time out if running for > 10 seconds.
//     (error, stdout, stderr) => {
//       if (error) {
//         return res.status(200).send(stderr);
//       }
//       return res.send(stdout);
//     }
//   );
// }

const docker = new Dockerode();

const ubuntuContainerName = "ubuuuuuntuContaiiiiiner";

/** Starts `container` if it is not already running, then returns it.
*/
function startUbuntuContainerIfNecessary(container) {
  return container.inspect().then(data => {
    if (data.State.Running) {
      console.log("Container is already started, returning it now");
      return container;
    } else {
      return container.start().then(container => {
        console.log("Successful start, returning the container");
        return container;
      }).catch(err => {
        let errMsg = "Failed start: " + err;
        console.log(errMsg);
        return Promise.reject(errMsg);
      });
    }
  }).catch(err => {
    let errMsg = "Error inspecting container: " + err;
    console.log(errMsg);
    return Promise.reject(errMsg);
  });
}

/** Creates, starts, and returns an ubuntu Docker container, or starts and returns the existing one if it already exists.
*/
function createAndStartUbuntuContainer(containerName) {
  return docker.createContainer({
    Image: 'ubuntu',
    AttachStdin: false,
    AttachStdout: true,
    AttachStderr: true,
    Cmd: ['bash', '-c', 'touch a; tail -f a'],
    name: containerName
  }).then(container => {
    console.log("Successful creation, starting and returning the container now");
    return startUbuntuContainerIfNecessary(container);
  }).catch(err => {
    if (err.json.message.includes(`Conflict. The container name "/${containerName}" is already in use`)) {
      console.log(`Container ${containerName} already exists, starting and returning it now`);
      let container = docker.getContainer(containerName);
      return startUbuntuContainerIfNecessary(container);
    } else {
      let errMsg = "Failed creation: " + err;
      console.log(errMsg);
      return Promise.reject(errMsg);
    }
  });
}

const ubuntuContainer = createAndStartUbuntuContainer(ubuntuContainerName);

function runCommandWithinUbuntuContainer(cmd) {
  let execHasTimedOut = false;
  let execHasCompleted = false;
  let result = null;
  let timeLimitInSeconds = 2;
  let timeLimitInMilliseconds = timeLimitInSeconds * 1000;

  // Constructor for the type of object to which our returned Promise should resolve/reject
  let createResult = (stdout, stderr, execWasSuccessful, errorMsg) => {
    return {
      stdout,
      stderr,
      execWasSuccessful,
      errorMsg
    }
  }

  return new Promise((resolve, reject) => {

    ubuntuContainer.then(container => {

      let timeoutFunction = setTimeout(() => {
        execHasTimedOut = true;
        console.log(`${timeLimitInSeconds} seconds have passed`);
        container.kill().then(container => {
          console.log("We killed the container");
          // TODO: remove container
        }).catch(err => {
          console.log("error killing the container");
        });
        if (!execHasCompleted) {
          console.log("Timed out!!!");
          resolve(createResult(null, null, false, `Timeout: exec took longer than ${timeLimitInSeconds} seconds`));
        }
      }, timeLimitInMilliseconds);

      container.exec(cmd, {
        stdout: true,
        stderr: true
      }).then(results => {
        execHasCompleted = true;
        if (!execHasTimedOut) {
          // TODO: kill and remove container
          clearTimeout(timeoutFunction);
          console.log("We did it!!!");
          resolve(createResult(results.stdout, results.stderr, true, null));
        } else {
          console.log("We already timed out");
          reject(createResult(
            null, null, false,
            "Timeout but this should never be seen since the promise should've been resolved already by `timeoutFunction`"));
        }
      }, err => {
        console.log("Error running exec: ", err);
        reject(createResult(null, null, false, err));
      });

    });

  }, err => {
    console.log("No ubuntu container available to run the command!");
    console.log("Error received: ", err);
    reject(createResult(null, null, false, `No container available due to error: ${err}`));
  });
}

function myTest() {
  let cmd = ['echo', 'Hi'];
  runCommandWithinUbuntuContainer(cmd).then(result => {
    console.log("Now result is: ", result);
  }).catch(err => {
    console.log("Error: ", err);
  });
}

myTest();

export async function compileLogger(req: $Request, res: $Response) {
  try {
    const userId = req.user.id;
    const loggerId = req.params.id;
    const logger = await Logger.findById(loggerId);
    if (!logger) {
      return res.status(400).json({
        error: `No logger found;`
      });
    }
    //console.log(logger);
    const dirName = "users/" + userId + "/" + loggerId;
    await exec("mkdir -p " + dirName);
    const escapedCode = jsesc(logger.code, {
      wrap: true
    });
    await exec(`echo ${escapedCode} > ${dirName}/${logger.className}.c`);
    const gcc = `gcc "${dirName}/${logger.className}.c" -o "${dirName}/${logger.className}" -lm`;
    //console.log(gcc);
    const result = await exec(gcc, {
      timeout: 10000
    });
    //console.error("result object", result);
    return res
      .status(200)
      .json({ error: false, stdout: result.stdout, stderr: result.stderr });
  } catch (err) {
    //console.error(err);
    return res
      .status(200)
      .json({ error: true, stdout: err.stdout, stderr: err.stderr });
  }
}

export async function runLogger(req: $Request, res: $Response) {
  try {
    const userId = req.user.id;
    const loggerId = req.params.id;
    const logger = await Logger.findById(loggerId);
    if (!logger) {
      return res.status(400).json({
        error: `No logger found;`
      });
    }
    const dirName = "users/" + userId + "/" + loggerId;
    const cmd = `"${dirName}/${logger.className}"`;
    const result = await exec(cmd, { timeout: 10000 });
    return res
      .status(200)
      .json({ error: false, stdout: result.stdout, stderr: result.stderr });
  } catch (err) {
    return res
      .status(200)
      .json({ error: true, stdout: err.stdout, stderr: err.stderr });
  }
}
<commit_msg>Use promise-timeout for a timeout on executing commands within the docker container<commit_after>// @flow
const Dockerode = require('simple-dockerode')

import type { $Request, $Response } from "express";

import { exec as execSync } from "child_process";
import jsesc from "jsesc";
import Logger from "../logger/logger.model";
import { promisify } from "util";
import { timeout, TimeoutError } from "promise-timeout";

const exec = promisify(execSync);

// Compile java code
// export function compile(req: $Request, res: $Response) {
//   const fileName = req.body.fileName;
//   let dirName = `users/${req.body.user._id}/`;
//   const dateTime = new Date();
//   dirName += dateTime.getMonth();
//   dirName += dateTime.getDate();
//   dirName += dateTime.getFullYear();
//
//   // make a directory for this user, if doesn't exist already
//   exec(
//     `mkdir -p ${dirName}`,
//     { timeout: 10000 }, // Process will time out if running for > 10 seconds.
//     (error, stdout, stderr) => {
//       if (error) {
//         return res.status(200).send(stderr);
//       }
//
//       const escapedCode = jsesc(req.body.code, {
//         wrap: true
//       });
//       exec(
//         `echo ${escapedCode} > ${dirName}/${fileName}`,
//         { timeout: 10000 }, // Process will time out if running for > 10 seconds.
//         (error, stdout, stderr) => {
//           if (error) {
//             return res.status(200).send(stderr);
//           }
//           compileJavaFile(`${dirName}/${fileName}`, dirName, res);
//         }
//       );
//     }
//   );
// }
//
// // run java code
// export function run(req: $Request, res: $Response) {
//   let dirName = `users/${req.body.user._id}/`;
//   const dateTime = new Date();
//   dirName += dateTime.getMonth();
//   dirName += dateTime.getDate();
//   dirName += dateTime.getFullYear();
//   const execFile = req.body.fileName.replace(".c", "");
//   const cmd = `"${dirName}/${execFile}"`;
//   exec(
//     cmd,
//     { timeout: 10000 }, // Process will time out if running for > 10 seconds.
//     (error, stdout, stderr) => {
//       if (error) {
//         return res.status(200).send(error);
//       }
//       return res.status(200).send({ stdout, stderr });
//     }
//   );
// }
//
// function compileJavaFile(srcFile, dirName, res) {
//   const execFile = srcFile.replace(".c", "");
//   // exec is asynchronous
//   exec(
//     `gcc "${srcFile}" -o "${execFile}"`,
//     { timeout: 10000 }, // Process will time out if running for > 10 seconds.
//     (error, stdout, stderr) => {
//       if (error) {
//         return res.status(200).send(stderr);
//       }
//       return res.send(stdout);
//     }
//   );
// }

const docker = new Dockerode();

const ubuntuContainerName = "ubuuuuuntuContaiiiiiner";

/** Starts `container` if it is not already running, then returns it.
*/
function startUbuntuContainerIfNecessary(container) {
  return container.inspect().then(data => {
    if (data.State.Running) {
      console.log("Container is already started, returning it now");
      return container;
    } else {
      return container.start().then(container => {
        console.log("Successful start, returning the container");
        return container;
      }).catch(err => {
        let errMsg = "Failed start: " + err;
        console.log(errMsg);
        return Promise.reject(errMsg);
      });
    }
  }).catch(err => {
    let errMsg = "Error inspecting container: " + err;
    console.log(errMsg);
    return Promise.reject(errMsg);
  });
}

/** Creates, starts, and returns an ubuntu Docker container, or starts and returns the existing one if it already exists.
*/
function createAndStartUbuntuContainer(containerName) {
  return docker.createContainer({
    Image: 'ubuntu',
    AttachStdin: false,
    AttachStdout: true,
    AttachStderr: true,
    Cmd: ['bash', '-c', 'touch a; tail -f a'],
    name: containerName
  }).then(container => {
    console.log("Successful creation, starting and returning the container now");
    return startUbuntuContainerIfNecessary(container);
  }).catch(err => {
    if (err.json.message.includes(`Conflict. The container name "/${containerName}" is already in use`)) {
      console.log(`Container ${containerName} already exists, starting and returning it now`);
      let container = docker.getContainer(containerName);
      return startUbuntuContainerIfNecessary(container);
    } else {
      let errMsg = "Failed creation: " + err;
      console.log(errMsg);
      return Promise.reject(errMsg);
    }
  });
}

const ubuntuContainer = createAndStartUbuntuContainer(ubuntuContainerName);

function runCommandWithinUbuntuContainer(cmd) {
  let result = null;
  let timeLimitInSeconds = 10;
  let timeLimitInMilliseconds = timeLimitInSeconds * 1000;

  // Constructor for the type of object to which our returned Promise should resolve/reject
  let createResult = (stdout, stderr, execWasSuccessful, errorMsg) => {
    return {
      stdout,
      stderr,
      execWasSuccessful,
      errorMsg
    }
  }

  return ubuntuContainer.then(
    container => {
      let execPromise = container.exec(cmd, {
        stdout: true,
        stderr: true
      });
      return timeout(execPromise, timeLimitInMilliseconds).then(
        results => {
          // TODO: kill and remove container
          console.log("We did it!!!");
          return createResult(results.stdout, results.stderr, true, null);
        },
        err => {
          if (err instanceof TimeoutError) {
            let errorMsg = `Timeout error: exec took longer than ${timeLimitInSeconds} seconds`;
            console.log(errorMsg);
            return createResult(null, null, false, errorMsg);
          } else {
            console.log("Error running exec: ", err);
            return createResult(null, null, false, err);
          }
        });
    },
    err => {
      console.log("No ubuntu container available to run the command!");
      console.log("Error received: ", err);
      return createResult(null, null, false, `No container available due to error: ${err}`);
    });
}

function myTest() {
  let cmd = ['echo', 'Hi'];
  runCommandWithinUbuntuContainer(cmd).then(result => {
    console.log("Now result is: ", result);
  }).catch(err => {
    console.log("Error: ", err);
  });
}

myTest();

export async function compileLogger(req: $Request, res: $Response) {
  try {
    const userId = req.user.id;
    const loggerId = req.params.id;
    const logger = await Logger.findById(loggerId);
    if (!logger) {
      return res.status(400).json({
        error: `No logger found;`
      });
    }
    //console.log(logger);
    const dirName = "users/" + userId + "/" + loggerId;
    await exec("mkdir -p " + dirName);
    const escapedCode = jsesc(logger.code, {
      wrap: true
    });
    await exec(`echo ${escapedCode} > ${dirName}/${logger.className}.c`);
    const gcc = `gcc "${dirName}/${logger.className}.c" -o "${dirName}/${logger.className}" -lm`;
    //console.log(gcc);
    const result = await exec(gcc, {
      timeout: 10000
    });
    //console.error("result object", result);
    return res
      .status(200)
      .json({ error: false, stdout: result.stdout, stderr: result.stderr });
  } catch (err) {
    //console.error(err);
    return res
      .status(200)
      .json({ error: true, stdout: err.stdout, stderr: err.stderr });
  }
}

export async function runLogger(req: $Request, res: $Response) {
  try {
    const userId = req.user.id;
    const loggerId = req.params.id;
    const logger = await Logger.findById(loggerId);
    if (!logger) {
      return res.status(400).json({
        error: `No logger found;`
      });
    }
    const dirName = "users/" + userId + "/" + loggerId;
    const cmd = `"${dirName}/${logger.className}"`;
    const result = await exec(cmd, { timeout: 10000 });
    return res
      .status(200)
      .json({ error: false, stdout: result.stdout, stderr: result.stderr });
  } catch (err) {
    return res
      .status(200)
      .json({ error: true, stdout: err.stdout, stderr: err.stderr });
  }
}
<|endoftext|>


<commit_before>// Sort of like a twisted protocol
jsio('import net');
jsio('import lib.Enum as Enum');

var ctx = jsio.__env.global;

exports.Protocol = Class(function() {
	this.connectionMade = function(isReconnect) {}
	this.dataReceived = function(data) {}
	this.connectionLost = function(reason) {}

	this._connectionMade = function() {
		this._isConnected = true;
		this.connectionMade.apply(this, arguments);
	}

	this._connectionLost = function() {
		this._isConnected = true;
		this.connectionLost.apply(this, arguments);
	}

	this.isConnected = function() {
		return !!this._isConnected;
	}

});

exports.Client = Class(function() {
	this.init = function(protocol) {
		this._protocol = protocol;
	}

	this.connect = function(transportName, opts) {
		this._remote = new this._protocol();
		this._remote._client = this;
		net.connect(this._remote, transportName, opts);
	}
});

// Sort of like a twisted factory
exports.Server = Class(function() {
	this.init = function(protocolClass) {
		this._protocolClass = protocolClass;
	}

	this.buildProtocol = function() {
		return new this._protocolClass();
	}

	this.listen = function(how, port) {
		return net.listen(this, how, port);
	}
});

exports.Transport = Class(function() {
	this._encoding = 'plain'
	this.write = function(data, encoding) {
		throw new Error("Not implemented");
	}
	this.getPeer = function() {
		throw new Error("Not implemented");
	}
	this.setEncoding = function(encoding) {
		this._encoding = encoding;
	}
	this.getEncoding = function() {
		return this._encoding;
	}
});

exports.Listener = Class(function() {
	this.init = function(server, opts) {
		this._server = server;
		this._opts = opts || {};
	}

	this.onConnect = function(transport) {
//		try {
			var p = this._server.buildProtocol();
			p.transport = transport;
			p.server = this._server;
			transport.protocol = p;
			transport.makeConnection(p);
			p._connectionMade();
//		} catch(e) {
//			logger.error(e);
//		}
	}

	this.listen = function() { throw new Error('Abstract class'); }
	this.stop = function() {}
});

exports.STATE = Enum('INITIAL', 'DISCONNECTED', 'CONNECTING', 'CONNECTED');
exports.Connector = Class(function() {
	this.init = function(protocol, opts) {
		this._protocol = protocol;
		this._opts = opts;
		this._state = exports.STATE.INITIAL;
	}

	this.getState = function() { return this._state; }

	this.onConnect = function(transport) {
		this._state = exports.STATE.CONNECTED;

		transport.makeConnection(this._protocol);
		this._protocol.transport = transport;
		try {
			this._protocol._connectionMade();
		} catch(e) {
			throw logger.error(e);
		}
	}

	this.onDisconnect = function(err) {
		var wasConnected = this._state == exports.STATE.CONNECTED;
		this._state = exports.STATE.DISCONNECTED;

		try {
			this._protocol._connectionLost(err, wasConnected);
		} catch(e) {
			throw logger.error(e);
		}
	}

	this.getProtocol = function() { return this._protocol; }
});
<commit_msg>Protocol interface should set _isConnected to false by default<commit_after>// Sort of like a twisted protocol
jsio('import net');
jsio('import lib.Enum as Enum');

var ctx = jsio.__env.global;

exports.Protocol = Class(function() {
	this.connectionMade = function(isReconnect) {}
	this.dataReceived = function(data) {}
	this.connectionLost = function(reason) {}

	this._connectionMade = function() {
		this._isConnected = true;
		this.connectionMade.apply(this, arguments);
	}

	this._connectionLost = function() {
		this._isConnected = true;
		this.connectionLost.apply(this, arguments);
	}

	this._isConnected = false;
	this.isConnected = function() {
		return !!this._isConnected;
	}

});

exports.Client = Class(function() {
	this.init = function(protocol) {
		this._protocol = protocol;
	}

	this.connect = function(transportName, opts) {
		this._remote = new this._protocol();
		this._remote._client = this;
		net.connect(this._remote, transportName, opts);
	}
});

// Sort of like a twisted factory
exports.Server = Class(function() {
	this.init = function(protocolClass) {
		this._protocolClass = protocolClass;
	}

	this.buildProtocol = function() {
		return new this._protocolClass();
	}

	this.listen = function(how, port) {
		return net.listen(this, how, port);
	}
});

exports.Transport = Class(function() {
	this._encoding = 'plain'
	this.write = function(data, encoding) {
		throw new Error("Not implemented");
	}
	this.getPeer = function() {
		throw new Error("Not implemented");
	}
	this.setEncoding = function(encoding) {
		this._encoding = encoding;
	}
	this.getEncoding = function() {
		return this._encoding;
	}
});

exports.Listener = Class(function() {
	this.init = function(server, opts) {
		this._server = server;
		this._opts = opts || {};
	}

	this.onConnect = function(transport) {
//		try {
			var p = this._server.buildProtocol();
			p.transport = transport;
			p.server = this._server;
			transport.protocol = p;
			transport.makeConnection(p);
			p._connectionMade();
//		} catch(e) {
//			logger.error(e);
//		}
	}

	this.listen = function() { throw new Error('Abstract class'); }
	this.stop = function() {}
});

exports.STATE = Enum('INITIAL', 'DISCONNECTED', 'CONNECTING', 'CONNECTED');
exports.Connector = Class(function() {
	this.init = function(protocol, opts) {
		this._protocol = protocol;
		this._opts = opts;
		this._state = exports.STATE.INITIAL;
	}

	this.getState = function() { return this._state; }

	this.onConnect = function(transport) {
		this._state = exports.STATE.CONNECTED;

		transport.makeConnection(this._protocol);
		this._protocol.transport = transport;
		try {
			this._protocol._connectionMade();
		} catch(e) {
			throw logger.error(e);
		}
	}

	this.onDisconnect = function(err) {
		var wasConnected = this._state == exports.STATE.CONNECTED;
		this._state = exports.STATE.DISCONNECTED;

		try {
			this._protocol._connectionLost(err, wasConnected);
		} catch(e) {
			throw logger.error(e);
		}
	}

	this.getProtocol = function() { return this._protocol; }
});
<|endoftext|>


<commit_before>/* global angular, console, _, Backbone, $ */
angular
	.module('boxie', ['ui', 'indx'])
	.factory('ObjsFactory', function () {
		var Objs = Backbone.Collection.extend({
			initialize: function (attributes, options) {
				if (options && options.box) { this.setBox(options.box); }
			},
			fetch: function () {
				var that = this,
					promise = $.Deferred(),
					ids = this.box.getObjIDs();
				that.box.getObj(ids).then(function (objs) {
					that.reset(objs);
					promise.resolve();
				});
				return promise;
			},
			setBox: function (box) {
				var that = this;
				this.box = box;
				that.box.on('all', function (e) {
					console.log(e, arguments)
				});
				this.box.on('obj-add', function (id) {
					that.box.getObj(id).then(function (obj) {
						that.add(obj);
					});
				});
				this.box.on('obj-remove', function (id) {
					that.box.getObj(id).then(function (obj) {
						that.remove(obj);
					});
				});
				return this;
			}
		});

		return Objs;
	})
	.factory('FilterObjsFactory', function (ObjsFactory) {
		var FilterObjs = Backbone.Collection.extend({
			initialize: function (attributes, options) {
				this.objs = new ObjsFactory(attributes, options);
				this.objs.on('add', this.add, this);
				this.objs.on('remove', this.remove, this);
				this.objs.on('reset', function (objs) {
					this.reset(objs.models);
				}, this);
				this._textFilter = '';
			},
			fetch: function () {
				return this.objs.fetch();
			},
			setBox: function (box) {
				this.objs.setBox(box);
				return this;
			},
			textFilter: function (str) {
				this._textFilter = str.toLocaleLowerCase();
				this.reset(this.objs.select(this.objPassFilter, this));
			},
			add: function (obj) {
				if (_.isArray(obj)) {
					_.each(obj, this.add, this);
				} else {
					if (this.objPassFilter(obj)) {
						Backbone.Collection.prototype.add.apply(this, arguments);
					}
				}
				return this;
			},
			objPassFilter: function (obj) {
				if (!obj) { return false; }
				return obj.id.toLocaleLowerCase().indexOf(this._textFilter) > -1;
			}
		});

		return FilterObjs;
	})
	.controller('root', function ($scope, client, utils, FilterObjsFactory) {
		'use strict';

		var box,
			u = utils,
			objs = new FilterObjsFactory();

		$scope.objs = objs;

		objs.on('add remove reset', function () {
			$update();
		});

		$scope.$watch('s.textFilter', function () {
			objs.textFilter($scope.s.textFilter);
		});

		$scope.$watch('selectedBox + selectedUser', function () {
			delete $scope.msg;
			if (!$scope.selectedUser) {
				$scope.msg = 'Please log in.';
			} else if (!$scope.selectedBox) {
				$scope.msg = 'Please select a box.';
			} else {
				client.store.getBox($scope.selectedBox)
					.then(function (box) { init(box); })
					.fail(function (e) { u.error('error ', e); $scope.msg = 'An error occured.'; });
			}

		});

		var init = function (box) {
			window.box = box;
			window.objs = objs;
			objs.setBox(box).fetch();
		};
		var $update = function () {
			u.safeApply($scope);
		};
		$scope.s = {
			page: 0,
			orderBy: 'id',
			orderReverse: false,
			perPage: 15,
			textFilter: ''
		}; // state
		$scope.Math = window.Math;

	}).filter('startFrom', function() {
		return function (input, start) {
			start = +start; //parse to int
			return input.slice(start);
		}
	});
<commit_msg>box in path<commit_after>/* global angular, console, _, Backbone, $ */
angular
	.module('boxie', ['ui', 'indx'])
	.factory('ObjsFactory', function () {
		var Objs = Backbone.Collection.extend({
			initialize: function (attributes, options) {
				if (options && options.box) { this.setBox(options.box); }
			},
			fetch: function () {
				var that = this,
					promise = $.Deferred(),
					ids = this.box.getObjIDs();
				that.box.getObj(ids).then(function (objs) {
					that.reset(objs);
					promise.resolve();
				});
				return promise;
			},
			setBox: function (box) {
				var that = this;
				this.box = box;
				that.box.on('all', function (e) {
					console.log(e, arguments)
				});
				this.box.on('obj-add', function (id) {
					that.box.getObj(id).then(function (obj) {
						that.add(obj);
					});
				});
				this.box.on('obj-remove', function (id) {
					that.box.getObj(id).then(function (obj) {
						that.remove(obj);
					});
				});
				return this;
			}
		});

		return Objs;
	})
	.factory('FilterObjsFactory', function (ObjsFactory) {
		var FilterObjs = Backbone.Collection.extend({
			initialize: function (attributes, options) {
				this.objs = new ObjsFactory(attributes, options);
				this.objs.on('add', this.add, this);
				this.objs.on('remove', this.remove, this);
				this.objs.on('reset', function (objs) {
					this.reset(objs.models);
				}, this);
				this._textFilter = '';
			},
			fetch: function () {
				return this.objs.fetch();
			},
			setBox: function (box) {
				this.objs.setBox(box);
				return this;
			},
			textFilter: function (str) {
				this._textFilter = str.toLocaleLowerCase();
				this.reset(this.objs.select(this.objPassFilter, this));
			},
			add: function (obj) {
				if (_.isArray(obj)) {
					_.each(obj, this.add, this);
				} else {
					if (this.objPassFilter(obj)) {
						Backbone.Collection.prototype.add.apply(this, arguments);
					}
				}
				return this;
			},
			objPassFilter: function (obj) {
				if (!obj) { return false; }
				return obj.id.toLocaleLowerCase().indexOf(this._textFilter) > -1;
			}
		});

		return FilterObjs;
	})
	.controller('root', function ($scope, $location, client, utils, FilterObjsFactory) {
		'use strict';

		//$location.html5Mode(false);

		$scope.$watch(function() {
			return $location.path();
		}, function (path) {
			$scope.selectedBox = path.split('/')[1];
		});

		var box,
			u = utils,
			objs = new FilterObjsFactory();

		$scope.objs = objs;

		objs.on('add remove reset', function () {
			$update();
		});

		$scope.$watch('s.textFilter', function () {
			objs.textFilter($scope.s.textFilter);
		});

		$scope.$watch('selectedBox + selectedUser', function () {
			delete $scope.msg;
			if (!$scope.selectedUser) {
				$scope.msg = 'Please log in.';
			} else if (!$scope.selectedBox) {
				$scope.msg = 'Please select a box.';
			} else {
				$location.path($scope.selectedBox);
				client.store.getBox($scope.selectedBox)
					.then(function (box) { init(box); })
					.fail(function (e) { u.error('error ', e); $scope.msg = 'An error occured.'; });
			}

		});

		var init = function (box) {
			window.box = box;
			window.objs = objs;
			objs.setBox(box).fetch();
		};
		var $update = function () {
			u.safeApply($scope);
		};
		$scope.s = {
			page: 0,
			orderBy: 'id',
			orderReverse: false,
			perPage: 15,
			textFilter: ''
		}; // state
		$scope.Math = window.Math;

	}).filter('startFrom', function() {
		return function (input, start) {
			start = +start; //parse to int
			return input.slice(start);
		}
	});
<|endoftext|>


<commit_before>"use strict";
const Metadata = require('../../lib/util/Metadata');
const lib = require('../../lib');
const util = require('../../lib/util/util');

/**
 * @alias query.Stream
 */
class Stream {

  /**
   * Creates a live updating object stream for a query
   * @alias query.Stream.createStream<T>
   * @param {EntityManager} entityManager The owning entity manager of this query
   * @param {string} query The query options
   * @param {string} query.query The serialized query
   * @param {string} query.bucket The Bucket on which the streaming query is performed
   * @param {string=} query.sort the sort string
   * @param {number=} query.limit the count, i.e. the number of items in the result
   * @param {number=} query.offset offset, i.e. the number of items to skip
   * @param {boolean=} query.initial Indicates if the initial result should be returned
   * @param {Object} options an object containing parameters
   * @return {Observable<RealtimeEvent<T>>} The query result as a live updating stream of objects
   */
  static createEventStream(entityManager, query, options) {
    options = options || {};
    options.reconnects = 0;
    return Stream.streamObservable(entityManager, query, options, (msg, next) => {
      if (msg.type == 'result') {
        const result = msg.data;
        msg.data.forEach((obj, index) => {
          const event = Object.assign({
            type: 'match',
            matchType: 'add',
            operation: 'none',
            initial: true
          }, msg);

          event.data = Stream._resolveObject(entityManager, obj);
          if (query.sort)
            event.index = index;

          next(event);
        });
      }

      if (msg.type == 'match') {
        msg.data = Stream._resolveObject(entityManager, msg.data);
        next(msg);
      }
    });
  }

  /**
   * Creates a live updating result stream for a query
   * @alias query.Stream.createStreamResult<T>
   * @param {EntityManager} entityManager The owning entity manager of this query
   * @param {string} query The query options
   * @param {string} query.query The serialized query
   * @param {string} query.bucket The Bucket on which the streaming query is performed
   * @param {string=} query.sort the sort string
   * @param {number=} query.limit the count, i.e. the number of items in the result
   * @param {number=} query.offset offset, i.e. the number of items to skip
   * @param {Object} options an object containing parameters
   * @return {Observable<Array<T>>} The query result as a live updating query result
   */
  static createResultStream(entityManager, query, options) {
    options = options || {};
    options.initial = true;
    options.matchTypes = 'all';
    options.operations = 'any';

    let result, ordered = !!query.sort;
    return Stream.streamObservable(entityManager, query, options, (event, next) => {
      if (event.type == 'result') {
        result = event.data.map(obj => Stream._resolveObject(entityManager, obj));
        next(result.slice());
      }

      if (event.type == 'match') {
        let obj = Stream._resolveObject(entityManager, event.data);

        if (event.matchType == 'remove' || event.matchType == 'changeIndex') {
          //if we have removed the instance our self, we do not have the cached instances anymore
          //therefore we can't find it anymore in the result by identity
          for (let i = 0, len = result.length; i < len; ++i) {
            if (result[i].id == event.data.id) {
              result.splice(i, 1);
              break;
            }
          }
        }

        if (event.matchType == 'add' || event.matchType == 'changeIndex') {
          ordered ? result.splice(event.index, 0, obj) : result.push(obj);
        }

        next(result.slice());
      }
    });
  }

  static streamObservable(entityManager, query, options, mapper, retryInterval) {
    options = Stream.parseOptions(options);
    let id = util.uuid();

    const socket = entityManager.entityManagerFactory.websocket;
    const observable = new lib.Observable(subscriber => {
      const stream = socket.openStream(entityManager.tokenStorage, id);

      stream.send(Object.assign({
        type: 'subscribe'
      }, query, options));

      let closed = false;
      const next = subscriber.next.bind(subscriber);
      const subscription = stream.subscribe({
        complete() {
          closed = true;
          subscriber.complete();
        },
        error(e) {
          closed = true;
          subscriber.error(e);
        },
        next(msg) {
          mapper(msg, next)
        }
      });

      return () => {
        if (!closed) { // send unsubscribe only when we aren't completed by the socket and call it only once
          stream.send({type: 'unsubscribe'});
          subscription.unsubscribe();
          closed = true;
        }
      }
    });

    return Stream.cachedObservable(observable, options);
  }

  static cachedObservable(observable, options) {
    let subscription = null;
    let observers = [];
    return new lib.Observable(observer => {
      if (!subscription) {
        let remainingRetries = options.reconnects;
        const subscriptionObserver = {
          next(msg) {
            observers.forEach(o => o.next(msg))
          },
          error(e) {
            observers.forEach(o => o.error(e))
          },
          complete() {
            if (remainingRetries !== 0) {
              remainingRetries = remainingRetries < 0 ? -1 : remainingRetries - 1;
              subscription = observable.subscribe(subscriptionObserver);
            } else {
              observers.forEach(o => o.complete())
            }
          }
        };
        subscription = observable.subscribe(subscriptionObserver);
      }
      observers.push(observer);
      return () => {
        observers.splice(observers.indexOf(observer), 1);
        if (!observers.length) {
          subscription.unsubscribe();
          subscription = null;
        }
      }
    });
  }

  /**
   * Valid options are:

initial: a Boolean indicating whether or not the initial result set should be delivered on creating the subscription

matchTypes: a list of match types

operations: a list of operations

   *
   *
   * @param {Object} options object containing options
   * @returns {Object} an object containing VALID options
   */
  static parseOptions(options) {
    options = options || {};

    const verified = {
      initial: options.initial === undefined || !!options.initial,
      matchTypes: Stream.normalizeMatchTypes(options.matchTypes),
      operations: Stream.normalizeOperations(options.operations),
      reconnects: Stream.normalizeReconnects(options.reconnects)
    };

    if (verified.matchTypes.indexOf('all') == -1 && verified.operations.indexOf('any') == -1) {
      throw new Error('Only subscriptions for either operations or matchTypes are allowed. You cannot subscribe to a query using matchTypes and operations at the same time!');
    }

    return verified;
  }

  static normalizeMatchTypes(list) {
    return Stream.normalizeSortedSet(list, 'all', "match types", ['add', 'change', 'changeIndex', 'match', 'remove']);
  }

  static normalizeReconnects(reconnects) {
    if (reconnects && !Number.isInteger(reconnects)) {
      throw new Error('non-integer values not allowed!');
    } else if (Number.isInteger(reconnects)) {
      reconnects = reconnects < 0 ? -1 : reconnects;
    } else {
      reconnects = -1;
    }
    return reconnects;
  }

  static normalizeOperations(list) {
    return Stream.normalizeSortedSet(list, 'any', "operations", ['delete', 'insert', 'none', 'update']);
  }

  static normalizeSortedSet(list, wildcard, itemType, allowedItems) {
    if (!list) {
      return [wildcard];
    }

    if (!Array.isArray(list)) {
      list = [list];
    }

    if (list.length == 0) {//undefined or empty list --> default value
      return [wildcard];
    }

    // sort, remove duplicates and check whether all values are allowed
    list.sort();
    let item;
    let lastItem = undefined;
    for (let i = list.length - 1; i >= 0; i--) {
      item = list[i];
      if (!item) {//undefined and null item in the list --> invalid!
        throw new Error('undefined and null not allowed!');
      }
      if (item === lastItem) {//remove duplicates
        list.splice(i, 1);
      }
      if (item === wildcard) {
        return [wildcard];
      }
      if (allowedItems.indexOf(item) == -1) {//raise error on invalid elements
        throw new Error(item + ' not allowed for ' + itemType + '! (permitted: ' + allowedItems + '.)');
      }
      lastItem = item;
    }

    return list;
  }

  static _resolveObject(entityManager, object) {
    const entity = entityManager.getReference(object.id);
    const metadata = Metadata.get(entity);
    if (!object.version) {
      metadata.setRemoved();
      entityManager.removeReference(entity);
    } else if (entity.version < object.version) {
      metadata.setJson(object, true);
    }
    return entity;
  }
}

module.exports = Stream;<commit_msg>removed internal attribute from event (type)<commit_after>"use strict";
const Metadata = require('../../lib/util/Metadata');
const lib = require('../../lib');
const util = require('../../lib/util/util');

/**
 * @alias query.Stream
 */
class Stream {

  /**
   * Creates a live updating object stream for a query
   * @alias query.Stream.createStream<T>
   * @param {EntityManager} entityManager The owning entity manager of this query
   * @param {string} query The query options
   * @param {string} query.query The serialized query
   * @param {string} query.bucket The Bucket on which the streaming query is performed
   * @param {string=} query.sort the sort string
   * @param {number=} query.limit the count, i.e. the number of items in the result
   * @param {number=} query.offset offset, i.e. the number of items to skip
   * @param {boolean=} query.initial Indicates if the initial result should be returned
   * @param {Object} options an object containing parameters
   * @return {Observable<RealtimeEvent<T>>} The query result as a live updating stream of objects
   */
  static createEventStream(entityManager, query, options) {
    options = options || {};
    options.reconnects = 0;
    return Stream.streamObservable(entityManager, query, options, (msg, next) => {
      let messageType = msg.type;
      delete msg.type;
      if (messageType == 'result') {
        const result = msg.data;
        msg.data.forEach((obj, index) => {
          const event = Object.assign({
            matchType: 'add',
            operation: 'none',
            initial: true
          }, msg);

          event.data = Stream._resolveObject(entityManager, obj);
          if (query.sort)
            event.index = index;

          next(event);
        });
      }

      if (messageType == 'match') {
        msg.data = Stream._resolveObject(entityManager, msg.data);
        next(msg);
      }
    });
  }

  /**
   * Creates a live updating result stream for a query
   * @alias query.Stream.createStreamResult<T>
   * @param {EntityManager} entityManager The owning entity manager of this query
   * @param {string} query The query options
   * @param {string} query.query The serialized query
   * @param {string} query.bucket The Bucket on which the streaming query is performed
   * @param {string=} query.sort the sort string
   * @param {number=} query.limit the count, i.e. the number of items in the result
   * @param {number=} query.offset offset, i.e. the number of items to skip
   * @param {Object} options an object containing parameters
   * @return {Observable<Array<T>>} The query result as a live updating query result
   */
  static createResultStream(entityManager, query, options) {
    options = options || {};
    options.initial = true;
    options.matchTypes = 'all';
    options.operations = 'any';

    let result, ordered = !!query.sort;
    return Stream.streamObservable(entityManager, query, options, (event, next) => {
      if (event.type == 'result') {
        result = event.data.map(obj => Stream._resolveObject(entityManager, obj));
        next(result.slice());
      }

      if (event.type == 'match') {
        let obj = Stream._resolveObject(entityManager, event.data);

        if (event.matchType == 'remove' || event.matchType == 'changeIndex') {
          //if we have removed the instance our self, we do not have the cached instances anymore
          //therefore we can't find it anymore in the result by identity
          for (let i = 0, len = result.length; i < len; ++i) {
            if (result[i].id == event.data.id) {
              result.splice(i, 1);
              break;
            }
          }
        }

        if (event.matchType == 'add' || event.matchType == 'changeIndex') {
          ordered ? result.splice(event.index, 0, obj) : result.push(obj);
        }

        next(result.slice());
      }
    });
  }

  static streamObservable(entityManager, query, options, mapper, retryInterval) {
    options = Stream.parseOptions(options);
    let id = util.uuid();

    const socket = entityManager.entityManagerFactory.websocket;
    const observable = new lib.Observable(subscriber => {
      const stream = socket.openStream(entityManager.tokenStorage, id);

      stream.send(Object.assign({
        type: 'subscribe'
      }, query, options));

      let closed = false;
      const next = subscriber.next.bind(subscriber);
      const subscription = stream.subscribe({
        complete() {
          closed = true;
          subscriber.complete();
        },
        error(e) {
          closed = true;
          subscriber.error(e);
        },
        next(msg) {
          mapper(msg, next)
        }
      });

      return () => {
        if (!closed) { // send unsubscribe only when we aren't completed by the socket and call it only once
          stream.send({type: 'unsubscribe'});
          subscription.unsubscribe();
          closed = true;
        }
      }
    });

    return Stream.cachedObservable(observable, options);
  }

  static cachedObservable(observable, options) {
    let subscription = null;
    let observers = [];
    return new lib.Observable(observer => {
      if (!subscription) {
        let remainingRetries = options.reconnects;
        const subscriptionObserver = {
          next(msg) {
            observers.forEach(o => o.next(msg))
          },
          error(e) {
            observers.forEach(o => o.error(e))
          },
          complete() {
            if (remainingRetries !== 0) {
              remainingRetries = remainingRetries < 0 ? -1 : remainingRetries - 1;
              subscription = observable.subscribe(subscriptionObserver);
            } else {
              observers.forEach(o => o.complete())
            }
          }
        };
        subscription = observable.subscribe(subscriptionObserver);
      }
      observers.push(observer);
      return () => {
        observers.splice(observers.indexOf(observer), 1);
        if (!observers.length) {
          subscription.unsubscribe();
          subscription = null;
        }
      }
    });
  }

  /**
   * Valid options are:

initial: a Boolean indicating whether or not the initial result set should be delivered on creating the subscription

matchTypes: a list of match types

operations: a list of operations

   *
   *
   * @param {Object} options object containing options
   * @returns {Object} an object containing VALID options
   */
  static parseOptions(options) {
    options = options || {};

    const verified = {
      initial: options.initial === undefined || !!options.initial,
      matchTypes: Stream.normalizeMatchTypes(options.matchTypes),
      operations: Stream.normalizeOperations(options.operations),
      reconnects: Stream.normalizeReconnects(options.reconnects)
    };

    if (verified.matchTypes.indexOf('all') == -1 && verified.operations.indexOf('any') == -1) {
      throw new Error('Only subscriptions for either operations or matchTypes are allowed. You cannot subscribe to a query using matchTypes and operations at the same time!');
    }

    return verified;
  }

  static normalizeMatchTypes(list) {
    return Stream.normalizeSortedSet(list, 'all', "match types", ['add', 'change', 'changeIndex', 'match', 'remove']);
  }

  static normalizeReconnects(reconnects) {
    if (reconnects && !Number.isInteger(reconnects)) {
      throw new Error('non-integer values not allowed!');
    } else if (Number.isInteger(reconnects)) {
      reconnects = reconnects < 0 ? -1 : reconnects;
    } else {
      reconnects = -1;
    }
    return reconnects;
  }

  static normalizeOperations(list) {
    return Stream.normalizeSortedSet(list, 'any', "operations", ['delete', 'insert', 'none', 'update']);
  }

  static normalizeSortedSet(list, wildcard, itemType, allowedItems) {
    if (!list) {
      return [wildcard];
    }

    if (!Array.isArray(list)) {
      list = [list];
    }

    if (list.length == 0) {//undefined or empty list --> default value
      return [wildcard];
    }

    // sort, remove duplicates and check whether all values are allowed
    list.sort();
    let item;
    let lastItem = undefined;
    for (let i = list.length - 1; i >= 0; i--) {
      item = list[i];
      if (!item) {//undefined and null item in the list --> invalid!
        throw new Error('undefined and null not allowed!');
      }
      if (item === lastItem) {//remove duplicates
        list.splice(i, 1);
      }
      if (item === wildcard) {
        return [wildcard];
      }
      if (allowedItems.indexOf(item) == -1) {//raise error on invalid elements
        throw new Error(item + ' not allowed for ' + itemType + '! (permitted: ' + allowedItems + '.)');
      }
      lastItem = item;
    }

    return list;
  }

  static _resolveObject(entityManager, object) {
    const entity = entityManager.getReference(object.id);
    const metadata = Metadata.get(entity);
    if (!object.version) {
      metadata.setRemoved();
      entityManager.removeReference(entity);
    } else if (entity.version < object.version) {
      metadata.setJson(object, true);
    }
    return entity;
  }
}

module.exports = Stream;<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');

module.exports = function(grunt) {
  // Unified Watch Object
  var watchFiles = {
    serverViews : ['app/views/**/*.*'],
    serverJS    : ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js'],
    clientViews : ['public/modules/**/views/**/*.html'],
    clientJS    : ['public/js/*.js', 'public/modules/**/*.js'],
    clientCSS   : ['public/modules/**/*.css'],
    lessFiles   : ['public/modules/**/*.less'],
    spriteFiles : ['public/modules/**/img/**/*.png'],
    mochaTests  : ['app/tests/**/*.js']
  };

  var webhookPort = process.env.WEBHOOK_PORT || 3000;
  // Project Configuration
  grunt.initConfig({
    pkg              : grunt.file.readJSON('package.json'),
    watch            : {
      serverViews : {
        files   : watchFiles.serverViews,
        options : {
          livereload : true
        }
      },
      serverJS    : {
        files   : watchFiles.serverJS,
        tasks   : ['jshint'],
        options : {
          livereload : true
        }
      },
      clientViews : {
        files   : watchFiles.clientViews,
        options : {
          livereload : true
        }
      },
      clientJS    : {
        files   : watchFiles.clientJS,
        tasks   : ['jshint'],
        options : {
          livereload : true
        }
      },
      clientCSS   : {
        files   : watchFiles.clientCSS,
        tasks   : ['csslint'],
        options : {
          livereload : true
        }
      },
      lessFiles   : {
        files   : watchFiles.lessFiles,
        tasks   : ['loadConfig', 'less:dev'],
        options : {}
      }
    },
    jshint           : {
      all : {
        src     : watchFiles.clientJS.concat(watchFiles.serverJS),
        options : {
          jshintrc : true
        }
      }
    },
    csslint          : {
      options : {
        csslintrc : '.csslintrc'
      },
      all     : {
        src : watchFiles.clientCSS
      }
    },
    uglify           : {
      options    : {
        mangle : false
      },
      production : {
        files : {
          'public/dist/build/application.min.js' : 'public/dist/build/application.js'
        }
      },
      material   : {
        files : {
          'public/dist/customized/angular-material/angular-material.min.js' : 'public/dist/customized/angular-material/angular-material.js'
        }
      }
    },
    cssmin           : {
      combine  : {
        files : {
          'public/dist/build/application.min.css' : '<%= applicationCSSFiles %>'
        }
      },
      material : {
        files : {
          'public/dist/customized/angular-material/angular-material.min.css' : 'public/dist/customized/angular-material/angular-material.css'
        }
      }
    },
    nodemon          : {
      dev : {
        script  : 'server.js',
        options : {
          nodeArgs : ['--debug'],
          ext      : 'js,html',
          ignore   : ['node_modules/**', 'public/**'],
          watch    : watchFiles.serverViews.concat(watchFiles.serverJS)
        }
      }
    },
    'node-inspector' : {
      custom : {
        options : {
          'web-port'          : 1337,
          'web-host'          : 'localhost',
          'debug-port'        : 5858,
          'save-live-edit'    : true,
          'no-preload'        : true,
          'stack-trace-limit' : 50,
          'hidden'            : []
        }
      }
    },
    ngAnnotate       : {
      production : {
        files : {
          'public/dist/build/application.js' : '<%= applicationJavaScriptFiles %>'
        }
      }
    },
    concurrent       : {
      default : ['nodemon', 'watch'],
      debug   : ['nodemon', 'watch', 'node-inspector'],
      dev     : ['nodemon', 'watch:lessFiles'],
      options : {
        logConcurrentOutput : true,
        limit               : 10
      }
    },
    env              : {
      test       : {
        NODE_ENV : 'test'
      },
      production : {
        NODE_ENV : 'production'
      },
      dev        : {
        NODE_ENV : 'development'
      }
    },
    mochaTest        : {
      src     : watchFiles.mochaTests,
      options : {
        reporter : 'spec',
        require  : 'server.js'
      }
    },
    less             : {
      dev : {
        options : {
          sourceMap       : true,
          dumpLineNumbers : 'comments',
          paths           : '<%= applicationLESSPaths %>',
          relativeUrls    : true
        },
        files   : {
          'public/dist/build/application.css' : '<%= applicationLESSFiles %>'
        }
      }
    },
    protractor       : {
      options : {
//        configFile: "node_modules/protractor/referenceConf.js", // Default config file
        keepAlive : true, // If false, the grunt process stops when the test fails.
        noColor   : false // If true, protractor will not use colors in its output.
      },
      dev     : {
        options : {
          configFile : 'protractor.conf.js'
        }
      }
    },
    gitpull          : {
      master : {
        options : {}
      }
    },
    forever          : {
      production : {
        options : {
          index  : 'server.js',
          logDir : 'logs'
        }
      }
    },
    shell            : {
      webhook : {
        command : 'webhook-deployer -p ' + webhookPort + ' >> logs/webhook.log',
        options : {
          async       : true,
          execOptions : {
            detached : true
          }
        }
      }
    },
    /* jshint camelcase: false */
    auto_install     : {
      local : {}
    },
    prompt           : {
      mysql : {
        options : {
          questions : [
            {
              config  : 'migrate.options.mysqlUser',
              type    : 'input',
              message : 'Enter mysql username: '
            },
            {
              config  : 'migrate.options.mysqlPass',
              type    : 'input',
              message : 'Enter mysql password: '
            },
            {
              config  : 'migrate.options.thumbsSrc',
              type    : 'input',
              message : 'Enter path from which user thumbnails should be moved to a new server path: '
            },
            {
              config  : 'migrate.options.env',
              type    : 'checkbox',
              message : 'Choose environment',
              choices : ['development', 'production']
            }
          ]
        }
      }
    }
  });

  require('load-grunt-tasks')(grunt);
  grunt.loadTasks('migrate');
  // Making grunt default to force in order not to break the project.
  grunt.option('force', true);

  // A Task for loading the configuration object
  grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function() {
    var init = require('./config/init')();
    var config = require('./config/config');
    grunt.config.set('applicationJavaScriptFiles', config.assets.js);
    grunt.config.set('applicationCSSFiles', config.assets.css);
    grunt.config.set('applicationLESSFiles', config.less.files);
    grunt.config.set('applicationLESSPaths', config.less.paths);
  });

  grunt.registerTask('dev', ['env:dev', 'lint', 'concurrent:dev']);
  grunt.registerTask('production', [
    'build', 'env:production', 'forever:production:stop', 'forever:production:start', 'webhook'
  ]);
  grunt.registerTask('production-stop', ['forever:production:stop']);
  grunt.registerTask('default', ['production']);

  // Lint task(s).
  grunt.registerTask('lint', ['jshint', 'csslint']);

  // Build task(s).
  grunt.registerTask('build', [
    'lint', 'env:dev', 'loadConfig', 'less:dev', 'ngAnnotate', 'uglify:production',
    'cssmin:combine'
  ]);
  grunt.registerTask('buildLess', ['env:dev', 'loadConfig', 'less:dev']);

  // Test tasks.
  grunt.registerTask('test', ['env:test', 'mochaTest']);

  grunt.registerTask('migrateUsers', ['prompt:mysql', 'migrate']);
  grunt.registerTask('pull-production', [
    'gitpull:master', 'build', 'auto_install:local', 'env:production',
    'forever:production:stop', 'forever:production:start'
  ]);
  grunt.registerTask('webhook', ['create-deploys', 'shell:webhook']);
  grunt.registerTask('protractor', ['protractor:dev']);

  grunt.task.registerTask('create-deploys', 'Sets basepath in deploys.json file', function() {
    var config = require(__dirname + '/deploysTpl.json');
    config.deploys[0].basepath = __dirname;
    fs.writeFileSync('deploys.json', JSON.stringify(config));
  });
};
<commit_msg>fixed too long lines<commit_after>'use strict';

var fs = require('fs');

module.exports = function(grunt) {
  // Unified Watch Object
  var watchFiles = {
    serverViews : ['app/views/**/*.*'],
    serverJS    : ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js'],
    clientViews : ['public/modules/**/views/**/*.html'],
    clientJS    : ['public/js/*.js', 'public/modules/**/*.js'],
    clientCSS   : ['public/modules/**/*.css'],
    lessFiles   : ['public/modules/**/*.less'],
    spriteFiles : ['public/modules/**/img/**/*.png'],
    mochaTests  : ['app/tests/**/*.js']
  };

  var webhookPort = process.env.WEBHOOK_PORT || 3000;
  // Project Configuration
  grunt.initConfig({
    pkg              : grunt.file.readJSON('package.json'),
    watch            : {
      serverViews : {
        files   : watchFiles.serverViews,
        options : {
          livereload : true
        }
      },
      serverJS    : {
        files   : watchFiles.serverJS,
        tasks   : ['jshint'],
        options : {
          livereload : true
        }
      },
      clientViews : {
        files   : watchFiles.clientViews,
        options : {
          livereload : true
        }
      },
      clientJS    : {
        files   : watchFiles.clientJS,
        tasks   : ['jshint'],
        options : {
          livereload : true
        }
      },
      clientCSS   : {
        files   : watchFiles.clientCSS,
        tasks   : ['csslint'],
        options : {
          livereload : true
        }
      },
      lessFiles   : {
        files   : watchFiles.lessFiles,
        tasks   : ['loadConfig', 'less:dev'],
        options : {}
      }
    },
    jshint           : {
      all : {
        src     : watchFiles.clientJS.concat(watchFiles.serverJS),
        options : {
          jshintrc : true
        }
      }
    },
    csslint          : {
      options : {
        csslintrc : '.csslintrc'
      },
      all     : {
        src : watchFiles.clientCSS
      }
    },
    uglify           : {
      options    : {
        mangle : false
      },
      production : {
        files : {
          'public/dist/build/application.min.js' : 'public/dist/build/application.js'
        }
      },
      material   : {
        files : {
          'public/dist/customized/angular-material/angular-material.min.js' :
            'public/dist/customized/angular-material/angular-material.js'
        }
      }
    },
    cssmin           : {
      combine  : {
        files : {
          'public/dist/build/application.min.css' : '<%= applicationCSSFiles %>'
        }
      },
      material : {
        files : {
          'public/dist/customized/angular-material/angular-material.min.css' :
            'public/dist/customized/angular-material/angular-material.css'
        }
      }
    },
    nodemon          : {
      dev : {
        script  : 'server.js',
        options : {
          nodeArgs : ['--debug'],
          ext      : 'js,html',
          ignore   : ['node_modules/**', 'public/**'],
          watch    : watchFiles.serverViews.concat(watchFiles.serverJS)
        }
      }
    },
    'node-inspector' : {
      custom : {
        options : {
          'web-port'          : 1337,
          'web-host'          : 'localhost',
          'debug-port'        : 5858,
          'save-live-edit'    : true,
          'no-preload'        : true,
          'stack-trace-limit' : 50,
          'hidden'            : []
        }
      }
    },
    ngAnnotate       : {
      production : {
        files : {
          'public/dist/build/application.js' : '<%= applicationJavaScriptFiles %>'
        }
      }
    },
    concurrent       : {
      default : ['nodemon', 'watch'],
      debug   : ['nodemon', 'watch', 'node-inspector'],
      dev     : ['nodemon', 'watch:lessFiles'],
      options : {
        logConcurrentOutput : true,
        limit               : 10
      }
    },
    env              : {
      test       : {
        NODE_ENV : 'test'
      },
      production : {
        NODE_ENV : 'production'
      },
      dev        : {
        NODE_ENV : 'development'
      }
    },
    mochaTest        : {
      src     : watchFiles.mochaTests,
      options : {
        reporter : 'spec',
        require  : 'server.js'
      }
    },
    less             : {
      dev : {
        options : {
          sourceMap       : true,
          dumpLineNumbers : 'comments',
          paths           : '<%= applicationLESSPaths %>',
          relativeUrls    : true
        },
        files   : {
          'public/dist/build/application.css' : '<%= applicationLESSFiles %>'
        }
      }
    },
    protractor       : {
      options : {
//        configFile: "node_modules/protractor/referenceConf.js", // Default config file
        keepAlive : true, // If false, the grunt process stops when the test fails.
        noColor   : false // If true, protractor will not use colors in its output.
      },
      dev     : {
        options : {
          configFile : 'protractor.conf.js'
        }
      }
    },
    gitpull          : {
      master : {
        options : {}
      }
    },
    forever          : {
      production : {
        options : {
          index  : 'server.js',
          logDir : 'logs'
        }
      }
    },
    shell            : {
      webhook : {
        command : 'webhook-deployer -p ' + webhookPort + ' >> logs/webhook.log',
        options : {
          async       : true,
          execOptions : {
            detached : true
          }
        }
      }
    },
    /* jshint camelcase: false */
    auto_install     : {
      local : {}
    },
    prompt           : {
      mysql : {
        options : {
          questions : [
            {
              config  : 'migrate.options.mysqlUser',
              type    : 'input',
              message : 'Enter mysql username: '
            },
            {
              config  : 'migrate.options.mysqlPass',
              type    : 'input',
              message : 'Enter mysql password: '
            },
            {
              config  : 'migrate.options.thumbsSrc',
              type    : 'input',
              message : 'Enter path from which user thumbnails should be moved to a new server path: '
            },
            {
              config  : 'migrate.options.env',
              type    : 'checkbox',
              message : 'Choose environment',
              choices : ['development', 'production']
            }
          ]
        }
      }
    }
  });

  require('load-grunt-tasks')(grunt);
  grunt.loadTasks('migrate');
  // Making grunt default to force in order not to break the project.
  grunt.option('force', true);

  // A Task for loading the configuration object
  grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function() {
    var init = require('./config/init')();
    var config = require('./config/config');
    grunt.config.set('applicationJavaScriptFiles', config.assets.js);
    grunt.config.set('applicationCSSFiles', config.assets.css);
    grunt.config.set('applicationLESSFiles', config.less.files);
    grunt.config.set('applicationLESSPaths', config.less.paths);
  });

  grunt.registerTask('dev', ['env:dev', 'lint', 'concurrent:dev']);
  grunt.registerTask('production', [
    'build', 'env:production', 'forever:production:stop', 'forever:production:start', 'webhook'
  ]);
  grunt.registerTask('production-stop', ['forever:production:stop']);
  grunt.registerTask('default', ['production']);

  // Lint task(s).
  grunt.registerTask('lint', ['jshint', 'csslint']);

  // Build task(s).
  grunt.registerTask('build', [
    'lint', 'env:dev', 'loadConfig', 'less:dev', 'ngAnnotate', 'uglify:production',
    'cssmin:combine'
  ]);
  grunt.registerTask('buildLess', ['env:dev', 'loadConfig', 'less:dev']);

  // Test tasks.
  grunt.registerTask('test', ['env:test', 'mochaTest']);

  grunt.registerTask('migrateUsers', ['prompt:mysql', 'migrate']);
  grunt.registerTask('pull-production', [
    'gitpull:master', 'build', 'auto_install:local', 'env:production',
    'forever:production:stop', 'forever:production:start'
  ]);
  grunt.registerTask('webhook', ['create-deploys', 'shell:webhook']);
  grunt.registerTask('protractor', ['protractor:dev']);

  grunt.task.registerTask('create-deploys', 'Sets basepath in deploys.json file', function() {
    var config = require(__dirname + '/deploysTpl.json');
    config.deploys[0].basepath = __dirname;
    fs.writeFileSync('deploys.json', JSON.stringify(config));
  });
};
<|endoftext|>


<commit_before>/**
 * sane, flexible threading for modern browsers
 * @version v0.1.0 - 2015-05-29
 * @author Kevin James <kevinjamesus86@gmail.com>
 * Copyright (c) 2015 Kevin James
 * Licensed under the MIT license.
 */
(function(root, factory) {

  // universal module definition
  if ('function' === typeof define && define.amd) {
    // AMD
    define([], factory);
  } else if ('object' === typeof exports) {
    // CommonJS
    module.exports = factory();
  } else {
    // browser global
    root.spawn = factory();
  }

})(this, function spawnFactory() {
  'use strict';

  // keep em close
  var Worker = window.Worker;
  var URL = window.URL || window.webkitURL;

  /**
   * Used for generating an worker with an empty body in
   * the case that a source path is provided
   */
  var noop = function() {};

  /**
   * @param {(string|Function)} src - worker source
   * @constructor
   */
  function Spawn(src) {
    this.isMainThread = true;

    var fn;

    if ('string' === typeof src) {
      fn = noop;
    } else if ('function' === typeof src) {
      fn = src;
      src = null;
    }

    this.file = URL.createObjectURL(new Blob([
      generateSpawnWorkerSource() + '(' + fn.toString() + ').call(self);'
    ], {
      type: 'text/javascript'
    }));

    this.worker = new Worker(this.file);
    this._init();

    // import the worker src if src was a string
    if (src) {
      this.importScripts(src);
    }
  }

  /**
   * Creates a relatively safe UUID
   * @return {string}
   */
  Spawn.prototype.uuid = function() {
    var prefix = this.isMainThread ? 'spawn_' : 'worker_';
    return prefix + Date.now().toString(32) + Math.random().toString(32);
  };

  /**
   * Main thread location information that is shared with
   * workers. Generated workers report an href of the form
   * `blob:http://srv/06af73c7-df3e-4c78-867e-937f829949c7`
   * which breaks relative path calls to importScripts
   *
   * Using spawn's utility method `importScripts` prefixes relative
   * script paths with the main threads href, solving this problem
   */
  Spawn.prototype.location = {
    origin: location.origin,
    originPath: location.href.match(/^(.*\/)?(?:$|(.+?)(?:(\.[^.]*$)|$))/)[1].
      replace(/\/?$/, '') + '/'
  };

  /**
   * Add an event listener for a given event
   *
   * @param {(Object|string)} event
   * @param {Function} handler
   * @return {Spawn}
   */
  Spawn.prototype.on = function(event, handler) {
    if ('object' === typeof event) {
      for (var e in event) {
        if (Object.prototype.hasOwnProperty.call(event, e)) {
          this.on(e, event[e]);
        }
      }
    } else {
      this.callbacks[event] = this.callbacks[event] || [];
      this.callbacks[event].push(handler);
    }
    return this;
  };

  /**
   * Triggers an event passing on data to the listeners. If an ackCallback is
   * provided it will be called with the value passed to a listeners responder
   *
   * @param {string} event
   * @param {*=} data
   * @param {Function=} ackCallback
   * @return {Spawn}
   */
  Spawn.prototype.emit = function(event, data, ackCallback, /* @private */ id) {
    var ack = false;
    if ('function' === typeof data) {
      ackCallback = data;
      data = null;
    } else if ('string' === typeof ackCallback) {
      id = ackCallback;
    }
    id = id || this.uuid();
    if ('function' === typeof ackCallback) {
      this.acks[id] = ackCallback;
      ack = true;
    }
    this.worker.postMessage({
      id: id,
      event: event,
      data: data,
      ack: ack
    });
    return this;
  };

  /**
   * Executes listeners attached to an event. If data is provided it will
   * be passed to every listener upon execution
   *
   * @param {string} event
   * @param {*} data
   * @param {number} id
   * @param {boolean} ack
   * @api private
   */
  Spawn.prototype._invoke = function(event, data, id, ack) {
    var fns = this.callbacks[event];
    var self = this;

    if (fns) {
      var length = fns.length,
        index = -1;
      while (++index < length) {
        fns[index](data, responder);
      }
    }

    // If the responder is invoked and the event can be acknowledged
    // then we must notify the emitter, passing on the data provided
    function responder(data) {
      if (ack) {
        self.emit('spawn_ack', data, id);
      }
    }
  };

  /**
   * Discards any tasks queued in the worker's event loop,
   * effectively closing the worker
   *
   * @return {Spawn}
   */
  Spawn.prototype.close = function() {
    if (this.isWorker) {
      this.emit('spawn_close');
      this.worker.close();
    } else {
      this.worker.terminate();
    }
    this.acks = {};
    this.callbacks = {};
    this.worker.onerror = this.worker.onmessage = null;
    URL.revokeObjectURL(this.file);
    return this;
  };

  /**
   * Imports one or more scripts into the worker's scope
   *
   * @param {...string} var_args - scripts to import
   * @return {Spawn}
   */
  Spawn.prototype['import'] =
  Spawn.prototype.importScripts = function() {
    var args = Array.prototype.slice.call(arguments, 0);

    if (this.isMainThread) {
      this.emit('spawn_import', args);
    } else {
      var length = args.length,
        index = -1,
        arg;

      while (++index < length) {
        arg = args[index];
        if ('/' === arg.charAt(0)) {
          args[index] = this.location.origin + arg;
        } else if (!/^https?:/.test(arg)) {
          args[index] = this.location.originPath + arg;
        }
      }

      importScripts.apply(self, args);
    }

    return this;
  };

  /**
   * Initialize Spawn
   * @api private
   */
  Spawn.prototype._init = function() {
    var self = this;

    self.acks = {};
    self.callbacks = {};

    self.worker.onmessage = function(e) {
      var data = e.data.data;
      var event = e.data.event;
      var id = e.data.id;

      switch (event) {
        case 'spawn_ack':
          self.acks[id](data);
          delete self.acks[id];
          break;
        case 'spawn_import':
          self.importScripts.apply(self, data);
          break;
        case 'spawn_close':
          self.close();
          break;
        default:
          self._invoke(event, data, id, e.data.ack);
      }
    };

    if (self.isMainThread) {
      /**
       * Deal with errors on the main thread so we have the option
       * of calling the ErrorEvent's `preventDefault()` method
       */
      self.worker.onerror = function(event) {
        self._invoke('error', event);
      };
    }
  };

  /**
   * Generate Spawn worker source code from .. Spawn
   */
  var generateSpawnWorkerSource = (function() {

    // Stringify Spawn's prototype so it
    // can be used in the worker source code
    var spawnPrototypeSource = Object.keys(Spawn.prototype).
      reduce(function(src, fn) {
        return src + 'Spawn.prototype.' + fn + '=' + stringify(Spawn.prototype[fn]) + ';';
      }, '');

    function stringify(val) {
      if ('function' === typeof val) {
        return val.toString();
      } else {
        return JSON.stringify(val);
      }
    }

    return function() {
      return [
        'self.spawn = (function() {',
          'function Spawn() {',
            'this.isWorker = true;',
            'this.worker = self;',
            'this._init();',
          '}',
          spawnPrototypeSource,
          'return new Spawn;',
        '})();'
      ].join('\n');
    };
  })();

  return function spawn(src) {
    return new Spawn(src);
  };
});<commit_msg>chore: prototype alias `fn` for a slightly better min<commit_after>/**
 * sane, flexible threading for modern browsers
 * @version v0.1.0 - 2015-05-29
 * @author Kevin James <kevinjamesus86@gmail.com>
 * Copyright (c) 2015 Kevin James
 * Licensed under the MIT license.
 */
(function(root, factory) {

  // universal module definition
  if ('function' === typeof define && define.amd) {
    // AMD
    define([], factory);
  } else if ('object' === typeof exports) {
    // CommonJS
    module.exports = factory();
  } else {
    // browser global
    root.spawn = factory();
  }

})(this, function spawnFactory() {
  'use strict';

  // keep em close
  var Worker = window.Worker;
  var URL = window.URL || window.webkitURL;

  /**
   * Used for generating an worker with an empty body in
   * the case that a source path is provided
   */
  var noop = function() {};

  /**
   * @param {(string|Function)} src - worker source
   * @constructor
   */
  function Spawn(src) {
    this.isMainThread = true;

    var fn;

    if ('string' === typeof src) {
      fn = noop;
    } else if ('function' === typeof src) {
      fn = src;
      src = null;
    }

    this.file = URL.createObjectURL(new Blob([
      generateSpawnWorkerSource() + '(' + fn.toString() + ').call(self);'
    ], {
      type: 'text/javascript'
    }));

    this.worker = new Worker(this.file);
    this._init();

    // import the worker src if src was a string
    if (src) {
      this.importScripts(src);
    }
  }

  // mins a little better
  Spawn.fn = Spawn.prototype;

  /**
   * Creates a relatively safe UUID
   * @return {string}
   */
  Spawn.fn.uuid = function() {
    var prefix = this.isMainThread ? 'spawn_' : 'worker_';
    return prefix + Date.now().toString(32) + Math.random().toString(32);
  };

  /**
   * Main thread location information that is shared with
   * workers. Generated workers report an href of the form
   * `blob:http://srv/06af73c7-df3e-4c78-867e-937f829949c7`
   * which breaks relative path calls to importScripts
   *
   * Using spawn's utility method `importScripts` prefixes relative
   * script paths with the main threads href, solving this problem
   */
  Spawn.fn.location = {
    origin: location.origin,
    originPath: location.href.match(/^(.*\/)?(?:$|(.+?)(?:(\.[^.]*$)|$))/)[1].
      replace(/\/?$/, '') + '/'
  };

  /**
   * Add an event listener for a given event
   *
   * @param {(Object|string)} event
   * @param {Function} handler
   * @return {Spawn}
   */
  Spawn.fn.on = function(event, handler) {
    if ('object' === typeof event) {
      for (var e in event) {
        if (Object.prototype.hasOwnProperty.call(event, e)) {
          this.on(e, event[e]);
        }
      }
    } else {
      this.callbacks[event] = this.callbacks[event] || [];
      this.callbacks[event].push(handler);
    }
    return this;
  };

  /**
   * Triggers an event passing on data to the listeners. If an ackCallback is
   * provided it will be called with the value passed to a listeners responder
   *
   * @param {string} event
   * @param {*=} data
   * @param {Function=} ackCallback
   * @return {Spawn}
   */
  Spawn.fn.emit = function(event, data, ackCallback, /* @private */ id) {
    var ack = false;
    if ('function' === typeof data) {
      ackCallback = data;
      data = null;
    } else if ('string' === typeof ackCallback) {
      id = ackCallback;
    }
    id = id || this.uuid();
    if ('function' === typeof ackCallback) {
      this.acks[id] = ackCallback;
      ack = true;
    }
    this.worker.postMessage({
      id: id,
      event: event,
      data: data,
      ack: ack
    });
    return this;
  };

  /**
   * Executes listeners attached to an event. If data is provided it will
   * be passed to every listener upon execution
   *
   * @param {string} event
   * @param {*} data
   * @param {number} id
   * @param {boolean} ack
   * @api private
   */
  Spawn.fn._invoke = function(event, data, id, ack) {
    var fns = this.callbacks[event];
    var self = this;

    if (fns) {
      var length = fns.length,
        index = -1;
      while (++index < length) {
        fns[index](data, responder);
      }
    }

    // If the responder is invoked and the event can be acknowledged
    // then we must notify the emitter, passing on the data provided
    function responder(data) {
      if (ack) {
        self.emit('spawn_ack', data, id);
      }
    }
  };

  /**
   * Discards any tasks queued in the worker's event loop,
   * effectively closing the worker
   *
   * @return {Spawn}
   */
  Spawn.fn.close = function() {
    if (this.isWorker) {
      this.emit('spawn_close');
      this.worker.close();
    } else {
      this.worker.terminate();
    }
    this.acks = {};
    this.callbacks = {};
    this.worker.onerror = this.worker.onmessage = null;
    URL.revokeObjectURL(this.file);
    return this;
  };

  /**
   * Imports one or more scripts into the worker's scope
   *
   * @param {...string} var_args - scripts to import
   * @return {Spawn}
   */
  Spawn.fn['import'] =
  Spawn.fn.importScripts = function() {
    var args = Array.prototype.slice.call(arguments, 0);

    if (this.isMainThread) {
      this.emit('spawn_import', args);
    } else {
      var length = args.length,
        index = -1,
        arg;

      while (++index < length) {
        arg = args[index];
        if ('/' === arg.charAt(0)) {
          args[index] = this.location.origin + arg;
        } else if (!/^https?:/.test(arg)) {
          args[index] = this.location.originPath + arg;
        }
      }

      importScripts.apply(self, args);
    }

    return this;
  };

  /**
   * Initialize Spawn
   * @api private
   */
  Spawn.fn._init = function() {
    var self = this;

    self.acks = {};
    self.callbacks = {};

    self.worker.onmessage = function(e) {
      var data = e.data.data;
      var event = e.data.event;
      var id = e.data.id;

      switch (event) {
        case 'spawn_ack':
          self.acks[id](data);
          delete self.acks[id];
          break;
        case 'spawn_import':
          self.importScripts.apply(self, data);
          break;
        case 'spawn_close':
          self.close();
          break;
        default:
          self._invoke(event, data, id, e.data.ack);
      }
    };

    if (self.isMainThread) {
      /**
       * Deal with errors on the main thread so we have the option
       * of calling the ErrorEvent's `preventDefault()` method
       */
      self.worker.onerror = function(event) {
        self._invoke('error', event);
      };
    }
  };

  /**
   * Generate Spawn worker source code from .. Spawn
   */
  var generateSpawnWorkerSource = (function() {

    // Stringify Spawn's prototype so it
    // can be used in the worker source code
    var spawnPrototypeSource = Object.keys(Spawn.fn).
      reduce(function(src, fn) {
        return src + 'Spawn.fn.' + fn + '=' + stringify(Spawn.fn[fn]) + ';';
      }, '');

    function stringify(val) {
      if ('function' === typeof val) {
        return val.toString();
      } else {
        return JSON.stringify(val);
      }
    }

    return function() {
      return [
        'self.spawn = (function() {',
          'function Spawn() {',
            'this.isWorker = true;',
            'this.worker = self;',
            'this._init();',
          '}',
          'Spawn.fn=Spawn.prototype;',
          spawnPrototypeSource,
          'return new Spawn;',
        '})();'
      ].join('\n');
    };
  })();

  return function spawn(src) {
    return new Spawn(src);
  };
});<|endoftext|>