<commit_before>
        // node built in
var   path = require('path')
        // lib code
    , run = require('../haxe-call')
    , state = require('../haxe-state')
    , compiler = require('../parsing/compiler')
        // atom related
    , Range = require('atom').Range

module.exports = {

        // Current linters
    linters: [],

        // Last compiler output errors
    compiler_errors: [],

    init: function() {

            // We need to watch editors in order to know
            // when a haxe file is saved and when to re-query haxe server
        this.editor_watch = atom.workspace.observeTextEditors(this._observe_editor.bind(this));

            // Run compiler once, one second after the haxe server is supposed to be started
            // A better option could be to have an explicit event to catch when the server is ready
        var time = atom.config.get('haxe.server_activation_start_delay');
        setTimeout(this._run_compiler.bind(this), (time + 1) * 1000.0);
    },

    dispose: function() {

        this.linters = [];
        this.editor_watch.dispose();
    },

    lint_file: function(linter, editor, temporary_path, done) {

            // Defer from 100ms because in many cases linters are triggered after
            // file save. In that case, we want to be sure the compiler will start
            // running before this code is executed.
        setTimeout((function() {

            if (linter.awaiting_info && linter.info_callback == null) {
                    // This linter is waiting from compiler info
                linter.info_callback = (function() {
                        // Called, after processing compiler output. Reset state and provide result
                    linter.awaiting_info = false;

                    this._provide_lint_items(editor, done);

                }).bind(this);
            }
            else {
                    // Called, after processing compiler output. Reset state and provide result
                linter.awaiting_info = false;

                this._provide_lint_items(editor, done);
            }

        }).bind(this), 100);
    },

    add_linter: function(linter) {
        this.linters.push(linter);
    },

    remove_linter: function(linter) {
        this.linters.splice(this.linters.indexOf(linter), 1);
    },

//Internal

    _observe_editor: function(editor) {
        var file_path = editor.getPath();
        var ext = path.extname(file_path);

        if (ext === '.hx') {
            var save_observer = editor.onDidSave((function(event) {
                file_path = event.path;

                // Run compiler when saving file
                this._run_compiler();

            }).bind(this));

            var destroy_observer = editor.onDidDestroy(function() {
                save_observer.dispose();
                destroy_observer.dispose();
            });
        }
    },

    _run_compiler: function() {
        //console.log("RUN COMPILER");

        var i, linter;

            // Inform linters that they should wait for compilation output
        for (i = 0; i < this.linters.length; i++) {
            linter = this.linters[i];
            linter.awaiting_info = true;
        }

        var options = {};
        var port = atom.config.get('haxe.server_port');

        var cwd  = state.hxml_cwd;
        var args = [];

        args.push('--no-output');

        if (cwd) {
            args.push('--cwd');
            args.push(cwd);
        }

        var hxml_args = state.hxml_as_args(options);
        if (hxml_args) {
            args = args.concat(hxml_args);
        }

        run.haxe(args).then((function(result) {

                // Extract errors from output
            if (result.err.length > 0) {
                this.compiler_errors = compiler.parse_output(result.err);
            } else {
                this.compiler_errors = [];
            }

            //console.log(this.compiler_errors);
            //console.log(this.linters);

                // Notify linters that they can now process the new output
            for (i = 0; i < this.linters.length; i++) {
                linter = this.linters[i];
                linter.awaiting_info = false;
                if (linter.info_callback != null) {
                    linter.info_callback();
                    linter.info_callback = null;
                }
            }
        }).bind(this));
    },

    _provide_lint_items: function(editor, done) {

        var message_lines, entry, info, i, j, l, editor_lines, start, end;

        var saved_path = editor.getBuffer().getPath();

        if (saved_path == null) {
            return done([]);
        }

        var result = [];

            // Compute items
        if (this.compiler_errors.length > 0) {
            for (i = 0; i < this.compiler_errors.length; i++) {
                info = this.compiler_errors[i];

                    // Only process the outputs related to the current file
                if (info.file_path == saved_path) {

                        // Error located on a characters range in the single line
                    if (info.location === 'characters') {

                            // Return one entry for each line of the message
                        message_lines = info.message.split("\n");
                        for (l = 0; l < message_lines.length; l++) {
                            entry = {
                                message:    message_lines[l],
                                line:       info.line - 1,
                                range:      new Range([info.line - 1, info.start], [info.line - 1, info.end]),
                                level:      'error',
                                linter:     'Haxe'
                            };

                            if (l == 0) {
                                entry.linter = 'Haxe';
                            }

                            result.push(entry);
                        }
                    }
                        // Error located on multiple lines
                    else if (info.location === 'lines') {
                            // Use the editor content to locate lint only on visible character ranges
                        if (editor_lines == null) {
                            editor_lines = editor.getText().split("\n");

                            for (j = info.start - 1; j < info.end; j++) {
                                    // Get start and end in line after trimming left and right of the line
                                start = editor_lines[j].length - editor_lines[j].replace(/^\s*/, '').length;
                                end = editor_lines[j].replace(/\s*$/, '').length;

                                    // Return one entry for each line of the message
                                message_lines = info.message.split("\n");
                                for (l = 0; l < message_lines.length; l++) {
                                    entry = {
                                        message:    message_lines[l],
                                        line:       j,
                                        range:      new Range([j, start], [j, end]),
                                        level:      'error'
                                    };

                                    if (l == 0) {
                                        entry.linter = 'Haxe';
                                    }

                                    result.push(entry);
                                }
                            }
                        }
                    }
                }
            }
        }

        done(result);
    }

}
<commit_msg>lint; fix jump to line with error from bottom right red dot<commit_after>
        // node built in
var   path = require('path')
        // lib code
    , run = require('../haxe-call')
    , state = require('../haxe-state')
    , compiler = require('../parsing/compiler')
        // atom related
    , Range = require('atom').Range

module.exports = {

        // Current linters
    linters: [],

        // Last compiler output errors
    compiler_errors: [],

    init: function() {

            // We need to watch editors in order to know
            // when a haxe file is saved and when to re-query haxe server
        this.editor_watch = atom.workspace.observeTextEditors(this._observe_editor.bind(this));

            // Run compiler once, one second after the haxe server is supposed to be started
            // A better option could be to have an explicit event to catch when the server is ready
        var time = atom.config.get('haxe.server_activation_start_delay');
        setTimeout(this._run_compiler.bind(this), (time + 1) * 1000.0);
    },

    dispose: function() {

        this.linters = [];
        this.editor_watch.dispose();
    },

    lint_file: function(linter, editor, temporary_path, done) {

            // Defer from 100ms because in many cases linters are triggered after
            // file save. In that case, we want to be sure the compiler will start
            // running before this code is executed.
        setTimeout((function() {

            if (linter.awaiting_info && linter.info_callback == null) {
                    // This linter is waiting from compiler info
                linter.info_callback = (function() {
                        // Called, after processing compiler output. Reset state and provide result
                    linter.awaiting_info = false;

                    this._provide_lint_items(editor, done);

                }).bind(this);
            }
            else {
                    // Called, after processing compiler output. Reset state and provide result
                linter.awaiting_info = false;

                this._provide_lint_items(editor, done);
            }

        }).bind(this), 100);
    },

    add_linter: function(linter) {
        this.linters.push(linter);
    },

    remove_linter: function(linter) {
        this.linters.splice(this.linters.indexOf(linter), 1);
    },

//Internal

    _observe_editor: function(editor) {
        var file_path = editor.getPath();
        var ext = path.extname(file_path);

        if (ext === '.hx') {
            var save_observer = editor.onDidSave((function(event) {
                file_path = event.path;

                // Run compiler when saving file
                this._run_compiler();

            }).bind(this));

            var destroy_observer = editor.onDidDestroy(function() {
                save_observer.dispose();
                destroy_observer.dispose();
            });
        }
    },

    _run_compiler: function() {
        //console.log("RUN COMPILER");

        var i, linter;

            // Inform linters that they should wait for compilation output
        for (i = 0; i < this.linters.length; i++) {
            linter = this.linters[i];
            linter.awaiting_info = true;
        }

        var options = {};
        var port = atom.config.get('haxe.server_port');

        var cwd  = state.hxml_cwd;
        var args = [];

        args.push('--no-output');

        if (cwd) {
            args.push('--cwd');
            args.push(cwd);
        }

        var hxml_args = state.hxml_as_args(options);
        if (hxml_args) {
            args = args.concat(hxml_args);
        }

        run.haxe(args).then((function(result) {

                // Extract errors from output
            if (result.err.length > 0) {
                this.compiler_errors = compiler.parse_output(result.err);
            } else {
                this.compiler_errors = [];
            }

            //console.log(this.compiler_errors);
            //console.log(this.linters);

                // Notify linters that they can now process the new output
            for (i = 0; i < this.linters.length; i++) {
                linter = this.linters[i];
                linter.awaiting_info = false;
                if (linter.info_callback != null) {
                    linter.info_callback();
                    linter.info_callback = null;
                }
            }
        }).bind(this));
    },

    _provide_lint_items: function(editor, done) {

        var message_lines, entry, info, i, j, l, editor_lines, start, end;

        var saved_path = editor.getBuffer().getPath();

        if (saved_path == null) {
            return done([]);
        }

        var result = [];

            // Compute items
        if (this.compiler_errors.length > 0) {
            for (i = 0; i < this.compiler_errors.length; i++) {
                info = this.compiler_errors[i];

                    // Only process the outputs related to the current file
                if (info.file_path == saved_path) {

                        // Error located on a characters range in the single line
                    if (info.location === 'characters') {

                            // Return one entry for each line of the message
                        message_lines = info.message.split("\n");
                        for (l = 0; l < message_lines.length; l++) {
                            entry = {
                                message:    message_lines[l],
                                line:       info.line,
                                range:      new Range([info.line - 1, info.start], [info.line - 1, info.end]),
                                level:      'error',
                                linter:     'Haxe'
                            };

                            if (l == 0) {
                                entry.linter = 'Haxe';
                            }

                            result.push(entry);
                        }
                    }
                        // Error located on multiple lines
                    else if (info.location === 'lines') {
                            // Use the editor content to locate lint only on visible character ranges
                        if (editor_lines == null) {
                            editor_lines = editor.getText().split("\n");

                            for (j = info.start - 1; j < info.end; j++) {
                                    // Get start and end in line after trimming left and right of the line
                                start = editor_lines[j].length - editor_lines[j].replace(/^\s*/, '').length;
                                end = editor_lines[j].replace(/\s*$/, '').length;

                                    // Return one entry for each line of the message
                                message_lines = info.message.split("\n");
                                for (l = 0; l < message_lines.length; l++) {
                                    entry = {
                                        message:    message_lines[l],
                                        line:       j + 1,
                                        range:      new Range([j, start], [j, end]),
                                        level:      'error'
                                    };

                                    if (l == 0) {
                                        entry.linter = 'Haxe';
                                    }

                                    result.push(entry);
                                }
                            }
                        }
                    }
                }
            }
        }

        done(result);
    }

}
<|endoftext|>


<commit_before>const loadingAreaSettings = require("./game-settings/loading_area_settings");
const getLaneStarts = require("./util/get_lane_starts");
const getLaneHeight = require("./util/get_lane_height");

function LoadingArea(settings) {
  this.settings = settings || loadingAreaSettings();
  this.state = {
    selected: 0
  };
  this.laneStarts = getLaneStarts(this.settings.laneCount, this.settings.height);
  this.laneHeight = getLaneHeight(this.settings.laneCount, this.settings.height);

}

LoadingArea.prototype.render = function (canvas) {
  //get a hold of the canvas
  //here we need to read some things from our state
  //draw our state to the canvas
  //return the gamestat even though it hasn't changed?

  var context = canvas[0].getContext("2d");
  context.clearRect(this.settings.x, this.settings.y, this.settings.width, this.settings.height);
  context.fillStyle = this.settings.bgColor;
  context.fillRect(this.settings.x, this.settings.y, this.settings.width, this.settings.height);
  //area1
  context.fillRect(this.settings.x, this.settings.y, this.settings.width, this.settings.height);
  this.renderSelected(canvas);

};
LoadingArea.prototype.renderSelected = function (canvas) {
  var context = canvas[0].getContext("2d");
  context.fillStyle = this.settings.selectedColor;
  context.fillRect(this.settings.x, this.laneStarts[this.state.selected], this.settings.width, this.laneHeight);
};

LoadingArea.prototype.laneDown = function () {
  var selected = this.state.selected;
  if (selected  !== 0) {this.state.selected -= 1;}
  return this.state;
};

LoadingArea.prototype.laneUp = function () {
  var selected = this.state.selected;
  if (selected  < this.settings.laneCount -1 ) {this.state.selected += 1;}
  return this.state;
};

module.exports = LoadingArea;
<commit_msg>refactored  rendering functions of loading area<commit_after>const loadingAreaSettings = require("./game-settings/loading_area_settings");
const getLaneStarts = require("./util/get_lane_starts");
const getLaneHeight = require("./util/get_lane_height");

function LoadingArea(settings) {
  this.settings = settings || loadingAreaSettings();
  this.state = {
    selected: 0
  };
  this.laneStarts = getLaneStarts(this.settings.laneCount, this.settings.height);
  this.laneHeight = getLaneHeight(this.settings.laneCount, this.settings.height);

}

LoadingArea.prototype.render = function (canvas) {
  //get a hold of the canvas
  //here we need to read some things from our state
  //draw our state to the canvas
  //return the gamestate even though it hasn't changed?
  this.renderBase(canvas);
  this.renderSelected(canvas);
};

LoadingArea.prototype.renderBase = function (canvas) {
  var context = canvas[0].getContext("2d");
  context.clearRect(this.settings.x, this.settings.y, this.settings.width, this.settings.height);
  context.fillStyle = this.settings.bgColor;
  context.fillRect(this.settings.x, this.settings.y, this.settings.width, this.settings.height);
};

LoadingArea.prototype.renderSelected = function (canvas) {
  var context = canvas[0].getContext("2d");
  context.fillStyle = this.settings.selectedColor;
  context.fillRect(this.settings.x, this.laneStarts[this.state.selected], this.settings.width, this.laneHeight);
};

LoadingArea.prototype.laneDown = function () {
  var selected = this.state.selected;
  if (selected  !== 0) {this.state.selected -= 1;}
  return this.state;
};

LoadingArea.prototype.laneUp = function () {
  var selected = this.state.selected;
  if (selected  < this.settings.laneCount -1 ) {this.state.selected += 1;}
  return this.state;
};

module.exports = LoadingArea;
<|endoftext|>


<commit_before>/**
 * This is a basic phantomJS script that will be used together
 * with the XSS auditor burp extender.
 *
 * This script launches a web server that listens by default
 * on 127.0.0.1:8093. The server listens for POST requests with
 * http-response data.
 *
 * http-response should contain base64 encoded HTTP response as
 * passed from burp intruder. The server will decode this data,
 * and build a WebPage bassed of the markup provided.
 *
 * The WebPage will be injected with the js-overrides.js file,
 * which contains triggers for suspicious JS functions, such as
 * alert, confirm, etc. The page will be evaluated, and the DOM
 * triggers will alert us of any suspicious JS.
*/
var system = require('system');
var fs = require('fs');

// Create xss object that will be used to track XSS information
var xss = new Object();
xss.value = 0;
xss.msg = "";

// Create webserver object
var webserver = require('webserver');
server = webserver.create();

// Server config details
var host = '127.0.0.1';
var port = '8093';

/**
 * parse incoming HTTP responses that are provided via BURP intruder.
 * data is base64 encoded to prevent issues passing via HTTP.
 *
 * This function appends the js-overrides.js file to all responses
 * to inject xss triggers into every page. Webkit will parse all responses
 * and alert us of any seemingly malicious Javascript execution, such as
 * alert, confirm, fromCharCode, etc.
 */
parsePage = function(data) {
	console.log("Beginning to parse page");
	var html_response = "";
	wp.content = data;

	// Evaluate page, rendering javascript
	xssInfo = wp.evaluate(function (wp) {
		// Return information from page, if necessary
	}, wp);

	if(xss) {
		// xss detected, return
		return xss;
	}
	return false;
};

/**
 * After retriving data it is important to reinitialize certain
 * variables, specifically those related to the WebPage objects.
 * Without reinitializing the WebPage object may contain old data,
 * and as such, trigger false-positive messages.
 */
reInitializeWebPage = function() {
	wp = new WebPage();
	xss = new Object();
	xss.value = 0;
	xss.msg = "";

	// web page settings necessary to adequately detect XSS
	wp.settings = {
		loadImages: true,
		localToRemoteUrlAccessEnabled: true,
		javascriptEnabled: true,
		webSecurityEnabled: true,
		XSSAuditingEnabled: true
	};

	// Custom handler for alert functionality
	wp.onAlert = function(msg) {
		console.log("On alert: " + msg);

		xss.value = 1;
		xss.msg += 'XSS found: Alert(' + msg + ')';
	};

	wp.onConsoleMessage = function(msg) {
		xss = true;
	    console.log('console: ' + msg);
	};

	return wp;
};

// Initialize webpage to ensure that all variables are
// initialized.
var wp = reInitializeWebPage();

// Start web server and listen for requests
var service = server.listen(host + ":" + port, function(request, response) {
	console.log("\nReceived request with method type: " + request.method);

	// At this point in time we're only concerned with POST requests
	// As such, only process those.
	if(request.method == "POST") {
		console.log("Processing Post Request");

		// Grab pageResponse from POST Data and base64 decode.
		// pass result to parsePage function to search for XSS.
		var pageResponse = request.post['http-response'];
		pageResponse = atob(pageResponse);
		xssResults = parsePage(pageResponse);

		// Return XSS Results
		if(xssResults) {
			// XSS is found, return information here
			response.statusCode = 200;
			response.write(JSON.stringify(xssResults));
			response.close();
		} else {
			response.statusCode = 201;
			response.write("No XSS found in response");
			response.close();
		}
	} else {
		response.statusCode = 500;
		response.write("Server is only designed to handle GET requests");
		response.close();
	}

	// Re-initialize webpage after parsing request
	wp = reInitializeWebPage();
	pageResponse = null;
	xssResults = null;
});

<commit_msg>disabled xssAuditing and web security<commit_after>/**
 * This is a basic phantomJS script that will be used together
 * with the XSS auditor burp extender.
 *
 * This script launches a web server that listens by default
 * on 127.0.0.1:8093. The server listens for POST requests with
 * http-response data.
 *
 * http-response should contain base64 encoded HTTP response as
 * passed from burp intruder. The server will decode this data,
 * and build a WebPage bassed of the markup provided.
 *
 * The WebPage will be injected with the js-overrides.js file,
 * which contains triggers for suspicious JS functions, such as
 * alert, confirm, etc. The page will be evaluated, and the DOM
 * triggers will alert us of any suspicious JS.
*/
var system = require('system');
var fs = require('fs');

// Create xss object that will be used to track XSS information
var xss = new Object();
xss.value = 0;
xss.msg = "";

// Create webserver object
var webserver = require('webserver');
server = webserver.create();

// Server config details
var host = '127.0.0.1';
var port = '8093';

/**
 * parse incoming HTTP responses that are provided via BURP intruder.
 * data is base64 encoded to prevent issues passing via HTTP.
 *
 * This function appends the js-overrides.js file to all responses
 * to inject xss triggers into every page. Webkit will parse all responses
 * and alert us of any seemingly malicious Javascript execution, such as
 * alert, confirm, fromCharCode, etc.
 */
parsePage = function(data) {
	console.log("Beginning to parse page");
	var html_response = "";
	wp.content = data;

	// Evaluate page, rendering javascript
	xssInfo = wp.evaluate(function (wp) {
		// Return information from page, if necessary
	}, wp);

	if(xss) {
		// xss detected, return
		return xss;
	}
	return false;
};

/**
 * After retriving data it is important to reinitialize certain
 * variables, specifically those related to the WebPage objects.
 * Without reinitializing the WebPage object may contain old data,
 * and as such, trigger false-positive messages.
 */
reInitializeWebPage = function() {
	wp = new WebPage();
	xss = new Object();
	xss.value = 0;
	xss.msg = "";

	// web page settings necessary to adequately detect XSS
	wp.settings = {
		loadImages: true,
		localToRemoteUrlAccessEnabled: true,
		javascriptEnabled: true,
		webSecurityEnabled: false,
		XSSAuditingEnabled: false
	};

	// Custom handler for alert functionality
	wp.onAlert = function(msg) {
		console.log("On alert: " + msg);

		xss.value = 1;
		xss.msg += 'XSS found: Alert(' + msg + ')';
	};

	wp.onConsoleMessage = function(msg) {
		xss = true;
	    console.log('console: ' + msg);
	};

	return wp;
};

// Initialize webpage to ensure that all variables are
// initialized.
var wp = reInitializeWebPage();

// Start web server and listen for requests
var service = server.listen(host + ":" + port, function(request, response) {
	console.log("\nReceived request with method type: " + request.method);

	// At this point in time we're only concerned with POST requests
	// As such, only process those.
	if(request.method == "POST") {
		console.log("Processing Post Request");

		// Grab pageResponse from POST Data and base64 decode.
		// pass result to parsePage function to search for XSS.
		var pageResponse = request.post['http-response'];
		pageResponse = atob(pageResponse);
		xssResults = parsePage(pageResponse);

		// Return XSS Results
		if(xssResults) {
			// XSS is found, return information here
			response.statusCode = 200;
			response.write(JSON.stringify(xssResults));
			response.close();
		} else {
			response.statusCode = 201;
			response.write("No XSS found in response");
			response.close();
		}
	} else {
		response.statusCode = 500;
		response.write("Server is only designed to handle GET requests");
		response.close();
	}

	// Re-initialize webpage after parsing request
	wp = reInitializeWebPage();
	pageResponse = null;
	xssResults = null;
});

<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const make = require('../make')
const random = require('../random')

class number extends make {
  static bool () {
    return random.bool()
  }

  static float () {
    let n
    if (random.chance(32)) {
      switch (random.number(4)) {
        case 0:
          n = random.range(Number.MIN_VALUE, Number.MAX_VALUE)
          break
        case 1:
          n = Math.pow(10, 1) / Math.pow(10, random.number(307))
          break
        case 2:
          n = Math.pow(2, random.float() * random.float() * 64)
          break
        case 3:
          n = Math.pow(10, random.range(1, 9)) / Math.pow(10, random.range(1, 9))
          break
      }
      return n
    }
    switch (random.number(6)) {
      default:
        n = random.float()
    }
    return n
  }

  static range () {
    return random.pick([1, 2, 3, 4, 6, 8, 16, 32, 64, number.tiny])
  }

  static frange (min, max, precision) {
    let x = Math.random() * (min - max) + max
    if (precision) {
      let power = Math.pow(10, precision || 0)
      x = Math.round(x * power) / power
    }
    return x
  }

  static tiny () {
    return Math.pow(2, random.number(12))
  }

  static unsigned () {
    if (random.chance(2)) {
      return Math.abs(number.any())
    }
    return Math.pow(2, random.number(random.number(65))) + random.number(3) - 1
  }

  static even (number) {
    return number % 2 === 1 ? ++number : number
  }

  static interesting () {
    return random.choose([
      [10, [-128, -1, 0, 1, 16, 32, 64, 100, 127]],
      [7, [-32768, -129, 128, 255, 256, 512, 1000, 1024, 4096, 32767]],
      [1, [-2147483648, -100663046, -32769, 32768, 65535, 65536, 100663045, 2147483647]]
    ])
  }

  static any () {
    let value = random.choose([
      [1, number.float],
      [1, number.unsigned],
      [1, [number.range, number.tiny]]
    ])
    return random.chance(10) ? -value : value
  }
}

module.exports = number
<commit_msg>Major rework of make.number<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const make = require('../make')
const random = require('../random')

class number extends make {
  /**
   * generate a number that exceeds the supplied boundary by +- 3
   * @param start
   * @param end
   * @private
   */
  static _exceed (start, end) {
    const value = random.item([start, end])
    const offset = random.range(-3, 3)
    return value + offset
  }

  /**
   * Generate a int8 [-128, 127]
   * @param bypass {boolean} - Determines if the range should be exceeded
   * @returns {number}
   */
  static int8 (bypass = false) {
    if (bypass || random.chance(50)) {
      return number._exceed(-128, 127)
    }

    return random.range(-128, 127)
  }

  /**
   * Generate a uint8 [0, 255]
   * @param bypass {boolean} - Determines if the range should be exceeded
   * @returns {number}
   */
  static uint8 (bypass = false) {
    if (bypass || random.chance(50)) {
      return number._exceed(0, 255)
    }

    return random.range(0, 255)
  }

  /**
   * Generate a int16 [-32768, 32767]
   * @param bypass {boolean} - Determines if the range should be exceeded
   * @returns {number}
   */
  static int16 (bypass = false) {
    if (bypass || random.chance(50)) {
      return number._exceed(-32768, 32767)
    }

    return random.range(-32768, 32767)
  }

  /**
   * Generate a uint16 [0, 65535]
   * @param bypass {boolean} - Determines if the range should be exceeded
   * @returns {*}
   */
  static uint16 (bypass = false) {
    if (bypass || random.chance(50)) {
      return number._exceed(0, 65535)
    }

    return random.range(-0, 65535)
  }

  /**
   * Generate a int32 [-2147483648, 2147483647]
   * @param bypass {boolean} - Determines if the range should be exceeded
   * @returns {number}
   */
  static int32 (bypass = false) {
    if (bypass || random.chance(50)) {
      return number._exceed(-2147483648, 2147483647)
    }

    return random.range(-2147483648, 2147483647)
  }

  /**
   * Generate a uint32 [0, 4294967295]
   * @param bypass {boolean} - Determines if the range should be exceeded
   * @returns {number}
   */
  static uint32 (bypass = false) {
    if (bypass || random.chance(50)) {
      return number._exceed(0, 4294967295)
    }

    return random.range(0, 4294967295)
  }

  static float () {
    let n
    if (random.chance(32)) {
      switch (random.number(4)) {
        case 0:
          n = random.range(Number.MIN_VALUE, Number.MAX_VALUE)
          break
        case 1:
          n = Math.pow(10, 1) / Math.pow(10, random.number(307))
          break
        case 2:
          n = Math.pow(2, random.float() * random.float() * 64)
          break
        case 3:
          n = Math.pow(10, random.range(1, 9)) / Math.pow(10, random.range(1, 9))
          break
      }
      return n
    }
    switch (random.number(6)) {
      default:
        n = random.float()
    }
    return n
  }

  static range () {
    return random.pick([1, 2, 3, 4, 6, 8, 16, 32, 64, number.tiny])
  }

  static frange (min, max, precision) {
    let x = Math.random() * (min - max) + max
    if (precision) {
      let power = Math.pow(10, precision || 0)
      x = Math.round(x * power) / power
    }
    return x
  }

  static tiny () {
    return Math.pow(2, random.number(12))
  }

  static unsigned () {
    if (random.chance(2)) {
      return Math.abs(number.any())
    }
    return Math.pow(2, random.number(random.number(65))) + random.number(3) - 1
  }

  static even (number) {
    return number % 2 === 1 ? ++number : number
  }

  static interesting () {
    return random.choose([
      [10, [-128, -1, 0, 1, 16, 32, 64, 100, 127]],
      [7, [-32768, -129, 128, 255, 256, 512, 1000, 1024, 4096, 32767]],
      [1, [-2147483648, -100663046, -32769, 32768, 65535, 65536, 100663045, 2147483647]]
    ])
  }

  static any () {
    let value = random.choose([
      [1, number.interesting],
      [1, number.float],
      [1, number.unsigned],
      [1, [number.range, number.tiny]]
    ])
    return random.chance(10) ? -value : value
  }
}

module.exports = number
<|endoftext|>


<commit_before>/*global WeakMap*/
import { arr, string, obj } from "lively.lang";
import { connect, disconnect } from "lively.bindings";

function signalBindings(obj, name, change) {
  // optimized lively.bindings.signal
  var conns = obj.attributeConnections;
  if (!conns) return
  conns = conns.slice();
  for (var i = 0; i < conns.length; i++) {
    if (conns[i].sourceAttrName === name)
      conns[i].update(change);
  }
}

function informMorph(changeManager, change, morph) {
  try {
    morph.onChange(change);
    signalBindings(morph, "change", change);
    var owner = morph.owner;
    while (owner) {
      owner.onSubmorphChange(change, morph);
      owner = owner.owner;
    }
  } catch (err) {
    console.error(`Error in informMorph: ${err.stack}`)
  }
}

class Change {
  constructor(target) {
    this.target = target;
    this.group = null;
  }
  get type() { return "abstract change" }
  apply() { throw new Error("Not yet implemented"); }
  reverseApply() { throw new Error("Not yet implemented"); }
}

export class GroupChange extends Change {
  constructor(target) {
    super(target);
    this.changes = [];
  }

  consumesChanges() { return true }

  addChange(c) {
    this.changes.push(c);
    c.group = this;
  }

  apply() {
    this.changes.slice().forEach(change => change.apply());
    return this;
  }

  reverseApply() {
    this.changes.slice().reverse().forEach(change => change.reverseApply());
    return this;
  }
}

export class ValueChange extends Change {

  get type() { return "setter" }

  constructor(target, prop, value, meta) {
    super(target);
    this.prop = prop;
    this.value = value;
    this.prevValue = null;
    this.meta = meta;
  }

  apply() {
    var {target, prop, value} = this;
    target[prop] = value;
  }

  reverseApply() {
    var {target, prop, prevValue} = this;
    target[prop] = prevValue;
  }
}

export class MethodCallChange extends GroupChange {

  get type() { return "method-call" }

  constructor(target, selector, args, undo, meta) {
    super(target);
    this.selector = selector;
    this.args = args;
    this.undo = undo;
    this.meta = meta;
  }

  apply() {
    var {target, selector, args} = this;
    target[selector].apply(target, args);
  }

  reverseApply() {
    if (!this.undo) return;
    if (typeof this.undo === "function") this.undo();
    else {
      var {target, selector, args} = this.undo;
      target[selector].apply(target, args);
    }
  }

}

export class ChangeManager {

  constructor() {
    this.reset();
  }

  reset() {
    this.changes = [];
    this.revision = 0;

    this.changeRecordersPerMorph = new WeakMap();
    this.changeRecorders = {};

    this.changeGroupStack = [];
    this.defaultMeta = {};
    this.metaStack = [];
  }

  changesFor(morph) { return this.changes.filter(c => c.target === morph); }

  apply(target, change) { change.apply(); }

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  // interface for adding changes, used by morphs

  doWithValueChangeMeta(meta, morph, doFn) {
     this.defaultMeta = meta;
     this.metaStack.push(meta);
     const res = doFn(morph);
     this.metaStack.pop();
     this.defaultMeta = arr.last(this.metaStack) || {};
     return res;
  }

  addValueChange(morph, prop, value, meta) {
    var change = new ValueChange(morph, prop, value, {...this.defaultMeta, ...meta});
    return this._record(morph, change);
  }

  addMethodCallChangeDoing(spec, morph, doFn) {
    var {target, selector, args, undo} = spec;
    if (!undo) undo = () => console.warn(`No undo recorded for ${target}.${selector}`);
    var change = new MethodCallChange(target, selector, args, undo, this.defaultMeta);
    morph.groupChangesWhile(change, doFn);
    return change;
  }

  _record(morph, change) {
    // FIXME
    if (change.hasOwnProperty("value")) {
      change.prevValue = morph._morphicState[change.prop];
      morph._morphicState[change.prop] = change.value;
    }

    morph.makeDirty();

    var grouping = arr.last(this.changeGroupStack);
    if (grouping && grouping.consumesChanges()) {
      grouping.addChange(change);
    } else {
      this.changes.push(change);
      morph._rev = ++this.revision;
      this.informChangeListeners(change)
    }
    informMorph(this, change, morph);

    return change;
  }

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  // group changes

  groupChangesWhile(targetMorph, groupChange, whileFn, record = true) {
    if (!groupChange) groupChange = new GroupChange(targetMorph);
    this.changeGroupStack.push(groupChange);
    try {
      whileFn();
      arr.remove(this.changeGroupStack, groupChange);
      return record ? this._record(targetMorph, groupChange) : groupChange;
    } catch (err) {
      arr.remove(this.changeGroupStack, groupChange);
      throw err;
    }
  }

  dontRecordChangesWhile(targetMorph, whileFn) {
    return this.groupChangesWhile(targetMorph, undefined, whileFn, false);
  }

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  // listen for changes / record changes

  recordChangesWhile(whileFn, optFilter) {
    var from = this.changes.length;
    whileFn();
    var changes = this.changes.slice(from, this.changes.length);
    return optFilter ? changes.filter(optFilter) : changes;
  }

  addChangeListener(listenFn) {
    connect(this, 'changeRecorded', listenFn, 'call', {
      updater: ($upd, change) => $upd(null, change)});
  }

  removeChangeListener(listenFn) {
    disconnect(this, 'changeRecorded', listenFn, 'call');
  }

  informChangeListeners(change) {
    // optimized version if lively.binings.signal
    var conns = this.attributeConnections;
    if (!conns) return;
    conns = conns.slice();
    for (var i = 0; i < conns.length; i++) {
      var c = conns[i];
      if (c.sourceAttrName === "changeRecorded")
        c.update(change);
    }
  }

  recordChangesStart(optFilter, optName = "") {
    // change recorder is a change listener that is identified by id

    // Recorder object to be used to record specific changes when they occur,
    // based on change listeners
    var id = obj.newKeyIn(this.changeRecorders, optName + "__change_recorder_" + Date.now()),
        listener = optFilter ?
          change => optFilter(change) && recorder.changes.push(change) :
          change => recorder.changes.push(change),
        recorder = this.changeRecorders[id] = {id, filter: optFilter, changes: [], listener};

    this.addChangeListener(listener);

    return recorder;
  }

  recordChangesStop(id) {
    if (!(id in this.changeRecorders)) return [];
    var {changes, listener} = this.changeRecorders[id];
    delete this.changeRecorders[id];
    this.removeChangeListener(listener);
    return changes;
  }

  recordChangesStartForMorph(morph, optFilter) {
    var recorder = this.recordChangesStart(optFilter, morph.id);

    // store recorder alongside morph for easy lookup and
    // to make it easy to just start / stop recordings
    // without having to manage listener storage and its lifetime

    var perMorph = this.changeRecordersPerMorph.get(morph);
    if (!perMorph) {
      perMorph = [];
      this.changeRecordersPerMorph.set(morph, perMorph);
    }
    perMorph.push(recorder.id);

    return recorder;
  }

  recordChangesStopForMorph(morph, optId) {
    var perMorph = this.changeRecordersPerMorph.get(morph);
    if (!perMorph || !perMorph.length) {
      console.warn(`Cannot endMorphChangeRecorder for morph ${morph}: recorder not found`)
      return [];
    }

    var id = optId;
    if (!optId) id = perMorph.pop();
    else arr.remove(perMorph, id);

    return this.recordChangesStop(id);
  }

}
<commit_msg>faster updating of changes<commit_after>/*global WeakMap*/
import { arr, string, obj } from "lively.lang";
import { connect, disconnect } from "lively.bindings";

function signalBindings(obj, name, change) {
  // optimized lively.bindings.signal
  var conns = obj.attributeConnections;
  if (!conns) return
  conns = conns.slice();
  for (var i = 0; i < conns.length; i++) {
    if (conns[i].sourceAttrName === name)
      conns[i].update(change);
  }
}

function informMorph(changeManager, change, morph) {
  try {
    morph.onChange(change);
    signalBindings(morph, "change", change);
    var owner = morph.owner;
    while (owner) {
      owner.onSubmorphChange(change, morph);
      owner = owner.owner;
    }
  } catch (err) {
    console.error(`Error in informMorph: ${err.stack}`)
  }
}

class Change {
  constructor(target) {
    this.target = target;
    this.group = null;
  }
  get type() { return "abstract change" }
  apply() { throw new Error("Not yet implemented"); }
  reverseApply() { throw new Error("Not yet implemented"); }
}

export class GroupChange extends Change {
  constructor(target) {
    super(target);
    this.changes = [];
  }

  consumesChanges() { return true }

  addChange(c) {
    this.changes.push(c);
    c.group = this;
  }

  apply() {
    this.changes.slice().forEach(change => change.apply());
    return this;
  }

  reverseApply() {
    this.changes.slice().reverse().forEach(change => change.reverseApply());
    return this;
  }
}

export class ValueChange extends Change {

  get type() { return "setter" }

  constructor(target, prop, value, meta) {
    super(target);
    this.prop = prop;
    this.value = value;
    this.prevValue = null;
    this.meta = meta;
  }

  apply() {
    var {target, prop, value} = this;
    target[prop] = value;
  }

  reverseApply() {
    var {target, prop, prevValue} = this;
    target[prop] = prevValue;
  }
}

export class MethodCallChange extends GroupChange {

  get type() { return "method-call" }

  constructor(target, selector, args, undo, meta) {
    super(target);
    this.selector = selector;
    this.args = args;
    this.undo = undo;
    this.meta = meta;
  }

  apply() {
    var {target, selector, args} = this;
    target[selector].apply(target, args);
  }

  reverseApply() {
    if (!this.undo) return;
    if (typeof this.undo === "function") this.undo();
    else {
      var {target, selector, args} = this.undo;
      target[selector].apply(target, args);
    }
  }

}

export class ChangeManager {

  constructor() {
    this.reset();
  }

  reset() {
    this.changes = [];
    this.changeRecordedListeners = [];
    this.revision = 0;

    this.changeRecordersPerMorph = new WeakMap();
    this.changeRecorders = {};

    this.changeGroupStack = [];
    this.defaultMeta = {};
    this.metaStack = [];
  }

  changesFor(morph) { return this.changes.filter(c => c.target === morph); }

  apply(target, change) { change.apply(); }

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  // interface for adding changes, used by morphs

  doWithValueChangeMeta(meta, morph, doFn) {
     this.defaultMeta = meta;
     this.metaStack.push(meta);
     const res = doFn(morph);
     this.metaStack.pop();
     this.defaultMeta = arr.last(this.metaStack) || {};
     return res;
  }

  addValueChange(morph, prop, value, meta) {
    var change = new ValueChange(morph, prop, value, {...this.defaultMeta, ...meta});
    return this._record(morph, change);
  }

  addMethodCallChangeDoing(spec, morph, doFn) {
    var {target, selector, args, undo} = spec;
    if (!undo) undo = () => console.warn(`No undo recorded for ${target}.${selector}`);
    var change = new MethodCallChange(target, selector, args, undo, this.defaultMeta);
    morph.groupChangesWhile(change, doFn);
    return change;
  }

  _record(morph, change) {
    // FIXME
    if (change.hasOwnProperty("value")) {
      change.prevValue = morph._morphicState[change.prop];
      morph._morphicState[change.prop] = change.value;
    }

    morph.makeDirty();

    var grouping = arr.last(this.changeGroupStack);
    if (grouping && grouping.consumesChanges()) {
      grouping.addChange(change);
    } else {
      this.changes.push(change);
      morph._rev = ++this.revision;
      this.informChangeListeners(change)
    }
    informMorph(this, change, morph);

    return change;
  }

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  // group changes

  groupChangesWhile(targetMorph, groupChange, whileFn, record = true) {
    if (!groupChange) groupChange = new GroupChange(targetMorph);
    this.changeGroupStack.push(groupChange);
    try {
      whileFn();
      arr.remove(this.changeGroupStack, groupChange);
      return record ? this._record(targetMorph, groupChange) : groupChange;
    } catch (err) {
      arr.remove(this.changeGroupStack, groupChange);
      throw err;
    }
  }

  dontRecordChangesWhile(targetMorph, whileFn) {
    return this.groupChangesWhile(targetMorph, undefined, whileFn, false);
  }

  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  // listen for changes / record changes

  recordChangesWhile(whileFn, optFilter) {
    var from = this.changes.length;
    whileFn();
    var changes = this.changes.slice(from, this.changes.length);
    return optFilter ? changes.filter(optFilter) : changes;
  }

  addChangeListener(listenFn) {
    arr.pushIfNotIncluded(this.changeRecordedListeners, listenFn);
  }

  removeChangeListener(listenFn) {
    arr.remove(this.changeRecordedListeners, listenFn);
  }

  informChangeListeners(change) {
    // optimized version if lively.binings.signal
    this.changeRecordedListeners.forEach(fn => fn(change));
  }

  recordChangesStart(optFilter, optName = "") {
    // change recorder is a change listener that is identified by id

    // Recorder object to be used to record specific changes when they occur,
    // based on change listeners
    var id = obj.newKeyIn(this.changeRecorders, optName + "__change_recorder_" + Date.now()),
        listener = optFilter ?
          change => optFilter(change) && recorder.changes.push(change) :
          change => recorder.changes.push(change),
        recorder = this.changeRecorders[id] = {id, filter: optFilter, changes: [], listener};

    this.addChangeListener(listener);

    return recorder;
  }

  recordChangesStop(id) {
    if (!(id in this.changeRecorders)) return [];
    var {changes, listener} = this.changeRecorders[id];
    delete this.changeRecorders[id];
    this.removeChangeListener(listener);
    return changes;
  }

  recordChangesStartForMorph(morph, optFilter) {
    var recorder = this.recordChangesStart(optFilter, morph.id);

    // store recorder alongside morph for easy lookup and
    // to make it easy to just start / stop recordings
    // without having to manage listener storage and its lifetime

    var perMorph = this.changeRecordersPerMorph.get(morph);
    if (!perMorph) {
      perMorph = [];
      this.changeRecordersPerMorph.set(morph, perMorph);
    }
    perMorph.push(recorder.id);

    return recorder;
  }

  recordChangesStopForMorph(morph, optId) {
    var perMorph = this.changeRecordersPerMorph.get(morph);
    if (!perMorph || !perMorph.length) {
      console.warn(`Cannot endMorphChangeRecorder for morph ${morph}: recorder not found`)
      return [];
    }

    var id = optId;
    if (!optId) id = perMorph.pop();
    else arr.remove(perMorph, id);

    return this.recordChangesStop(id);
  }

}
<|endoftext|>


<commit_before>'use babel';
/*@flow */

var Mocha = require('mocha');
var fs = require('fs');
var path = require('path');

export function mochaRun() {
  var promise = new Promise(function(resolve, reject) {
    // TODO add to settings
    this.mocha = new Mocha({
        ui: 'bdd'
    });
    this.results = Ich[{
      type: 'Error',
      text: 'Something went wrong',
      range: [[0,0], [0,1]],
      filePath: editorPath
    }];

    // TODO add to settings
    var testDir = './test';

    fs.readdirSync(testDir)
        .filter(function (file) {
            // Only keep the .js files
            return file.substr(-3) === '.js';
        })
        .forEach(function (file) {
            this.mocha.addFile(
                path.join(testDir, file)
            );
        });

    this.runner = this.mocha.run(function (failures) {
        process.on('exit', function (exitCode) {
            if(exitCode !== 0) {
              reject(exitCode);
            }
            process.exit(failures);
        });
        process.on('SIGINT', function (error) {
            reject(error);
            process.kill('SIGINT'); // calls runner.abort()
            process.kill('SIGTERM'); // if that didn't work, we're probably in an infinite loop, so make it die.
        });
    });

    this.runner.on('start', function (start) {
        console.log('START', start);
    });
    this.runner.on('end', function (end) {
        console.log('END', end);
        resolve(results);
    });
    this.runner.on('suite', function (suite) {
        console.log('SUITE START', suite);
    });
    this.runner.on('suite end', function (suite) {
        console.log('SUITE END', suite);
    });
    this.runner.on('test', function (test) {
        console.log('TEST START', test);
    });
    this.runner.on('test end', function (test) {
        console.log('TEST END', test);
    });
    this.runner.on('hook', function (hook) {
        console.log('HOOK START', hook);
    });
    this.runner.on('hook end', function (hook) {
        console.log('HOOK END', hook);
    });
    this.runner.on('pass', function (test) {
        console.log('PASS', test);
    });
    this.runner.on('fail', function (test, error) {
        console.log('FAIL', test, error);
    });
    this.runner.on('pending', function (test) {
        console.log('PENDING', test);
    });
  });

  promise.then(function(result) {
    console.log(result); // "Stuff worked!"
    return this.results;
  }, function(err) {
    console.log(err); // Error: "It broke"
    return this.results;
  });

  return promise;
}
<commit_msg>remove error :)<commit_after>'use babel';
/*@flow */

var Mocha = require('mocha');
var fs = require('fs');
var path = require('path');

export function mochaRun() {
  var promise = new Promise(function(resolve, reject) {
    // TODO add to settings
    this.mocha = new Mocha({
        ui: 'bdd'
    });
    this.results = [{
      type: 'Error',
      text: 'Something went wrong',
      range: [[0,0], [0,1]],
      filePath: editorPath
    }];

    // TODO add to settings
    var testDir = './test';

    fs.readdirSync(testDir)
        .filter(function (file) {
            // Only keep the .js files
            return file.substr(-3) === '.js';
        })
        .forEach(function (file) {
            this.mocha.addFile(
                path.join(testDir, file)
            );
        });

    this.runner = this.mocha.run(function (failures) {
        process.on('exit', function (exitCode) {
            if(exitCode !== 0) {
              reject(exitCode);
            }
            process.exit(failures);
        });
        process.on('SIGINT', function (error) {
            reject(error);
            process.kill('SIGINT'); // calls runner.abort()
            process.kill('SIGTERM'); // if that didn't work, we're probably in an infinite loop, so make it die.
        });
    });

    this.runner.on('start', function (start) {
        console.log('START', start);
    });
    this.runner.on('end', function (end) {
        console.log('END', end);
        resolve(results);
    });
    this.runner.on('suite', function (suite) {
        console.log('SUITE START', suite);
    });
    this.runner.on('suite end', function (suite) {
        console.log('SUITE END', suite);
    });
    this.runner.on('test', function (test) {
        console.log('TEST START', test);
    });
    this.runner.on('test end', function (test) {
        console.log('TEST END', test);
    });
    this.runner.on('hook', function (hook) {
        console.log('HOOK START', hook);
    });
    this.runner.on('hook end', function (hook) {
        console.log('HOOK END', hook);
    });
    this.runner.on('pass', function (test) {
        console.log('PASS', test);
    });
    this.runner.on('fail', function (test, error) {
        console.log('FAIL', test, error);
    });
    this.runner.on('pending', function (test) {
        console.log('PENDING', test);
    });
  });

  promise.then(function(result) {
    console.log(result); // "Stuff worked!"
    return this.results;
  }, function(err) {
    console.log(err); // Error: "It broke"
    return this.results;
  });

  return promise;
}
<|endoftext|>


<commit_before>"use strict";

var _                     = require('lodash')
  , Promise               = require('bluebird')
  , EventEmitter          = require('events').EventEmitter
  , winston               = require('winston')
  , crypto                = require('crypto')
  , urljoin               = require('url-join')
  , config                = require('config')
  , filenamifyUrl         = require('filenamify-url')
  , Request               = require('./Request')
  , MultiUseContainer     = require('./MultiUseContainer')
  , SingleUseContainer    = require('./SingleUseContainer')
  ;

class Worker extends EventEmitter {
  constructor(id, remote, options) {
    super();

    this.id = id;
    this.remote = remote;

    this.options = options || {};

    // todo [akamel] what is this for?
    // - when does this worker ever get deleted?
    // - maybe on idle?
    this.once('finish', () => delete store[id]);
  }

  container() {
    if (!this.__container) {
      let single_use    = !this.options['keep-alive']
        , remote        = this.remote
        , ContainerType = single_use? SingleUseContainer : MultiUseContainer
        // todo [akamel] we need to have diff disk per branch
        , checksum      = crypto
                            .createHash('sha1')
                            .update(this.id)
                            .digest('hex')
        , key           = filenamifyUrl(this.id, { replacement : '.' }) + '.' + checksum
        , disk          = urljoin('.disk', key);
        ;

      // console.log(this.id, this.remote, filenamifyUrl(this.id), filenamifyUrl(this.remote));

      let container = new ContainerType(this.remote, disk, {
          branch  : this.options.branch
        , key     : key
      });

      let kill = () => {
        container.removeListener('stats', monitor);
        container.sigkill();

        // decomission this container right away
        delete this.__container;
      };

      let monitor = (stats) => {
        let max_usage = config.get('worker.max-memory') * 1024 * 1024;
        if (stats.memory_stats.usage > max_usage) {
          winston.info('kill - memory limit', this.id);
          return kill();
        }

        let max_idle = config.get('worker.max-idle') * 1000;
        if (stats.idle > max_idle) {
          winston.info('kill - idle limit', this.id);
          return kill();
        }
      };

      container.on('stats', monitor);

      let term = (msg) => {
        winston.info('container terminated:', msg.id);
        // this container died, unknown reason, decomision
        container.end(new Error(`container terminated`));

        if (this.__container === container) {
          delete this.__container;
        }
      }

      container.once('stop', () => {});
      container.once('die', term);

      this.__container = container;
    }

    return this.__container;
  }

  acquire(req) {
    let container = this.container();

    container.acquire(req);

    return container;
  }

  handle(req, res) {
    return req.__obj
            .acl()
            .then(() => this.acquire(req))
            .then((container) => {
              // todo [akamel] get oauth token, run private code
              let blob = container
                          .repository()
                          .then((repository) => {
                            let content   = req.__obj.doc.blob
                              , filename  = req.__obj.doc.filename
                              , branch    = req.__obj.doc.branch
                              , opts      = {
                                                branch    : branch
                                              , token     : undefined /*token*/
                                              , populate  : {
                                                  manual : true
                                              }
                                            }
                              ;

                            if (content) {
                              return repository.hotreload(filename, content, opts);
                            }

                            return repository.blob(filename, opts);
                          });

              Promise
                .all([
                    blob
                  , container.build()
                ])
                .spread((blob, build) => {
                  return container
                          .write(req.__obj.doc.filename, blob.content)
                          .then(() => {
                            console.log('as;diua8s', blob);
                            req.__obj.doc.manual = blob.manual;

                            delete req.__obj.doc.blob;
                          });
                })
                .then(() => container.await())
                .then(() => container.handle(req, res))
                .catch((err) => {
                  winston.error(err);
                  req.__obj.decline(err, req, res);
                });
            });
  }

  static get(req, res) {
    return Promise.try(() => {
      let can_cohost = req.__obj.canCoHost();

      // todo [akamel] branch should be part of the key because of package.json
      let key = can_cohost
                  ? req.__obj.remote
                  : `${req.__obj.remote}#${req.__obj.id}`;

      if (!store[key]) {
        store[key] = new Worker(key, req.__obj.remote, {
            'keep-alive'  : can_cohost
          , 'branch'      : req.__obj.branch
        });
      }

      return store[key];
    });
  }

  static handle(req, res) {
    req.__obj = new Request(req, res);

    return Worker
            .get(req, res)
            .then((worker) => worker.handle(req, res))
            .catch((err) => {
              winston.error(err);

              res.statusCode = 500;
              res.write(err.toString());
              res.end();
            });
  }

  static stats() {
    return _.map(store, (worker) => {
      return {
          id    : worker.id
        , stats : _.get(worker, '__container.stats')
      }
    });
  }
}

var store = {};

module.exports = Worker;<commit_msg>wipe pre-build<commit_after>"use strict";

var _                     = require('lodash')
  , Promise               = require('bluebird')
  , EventEmitter          = require('events').EventEmitter
  , winston               = require('winston')
  , crypto                = require('crypto')
  , urljoin               = require('url-join')
  , config                = require('config')
  , filenamifyUrl         = require('filenamify-url')
  , Request               = require('./Request')
  , MultiUseContainer     = require('./MultiUseContainer')
  , SingleUseContainer    = require('./SingleUseContainer')
  ;

class Worker extends EventEmitter {
  constructor(id, remote, options) {
    super();

    this.id = id;
    this.remote = remote;

    this.options = options || {};

    // todo [akamel] what is this for?
    // - when does this worker ever get deleted?
    // - maybe on idle?
    this.once('finish', () => delete store[id]);
  }

  container() {
    if (!this.__container) {
      let single_use    = !this.options['keep-alive']
        , remote        = this.remote
        , ContainerType = single_use? SingleUseContainer : MultiUseContainer
        // todo [akamel] we need to have diff disk per branch
        , checksum      = crypto
                            .createHash('sha1')
                            .update(this.id)
                            .digest('hex')
        , key           = filenamifyUrl(this.id, { replacement : '.' }) + '.' + checksum
        , disk          = urljoin('.disk', key);
        ;

      // console.log(this.id, this.remote, filenamifyUrl(this.id), filenamifyUrl(this.remote));

      let container = new ContainerType(this.remote, disk, {
          branch  : this.options.branch
        , key     : key
      });

      let kill = () => {
        container.removeListener('stats', monitor);
        container.sigkill();

        // decomission this container right away
        delete this.__container;
      };

      let monitor = (stats) => {
        let max_usage = config.get('worker.max-memory') * 1024 * 1024;
        if (stats.memory_stats.usage > max_usage) {
          winston.info('kill - memory limit', this.id);
          return kill();
        }

        let max_idle = config.get('worker.max-idle') * 1000;
        if (stats.idle > max_idle) {
          winston.info('kill - idle limit', this.id);
          return kill();
        }
      };

      container.on('stats', monitor);

      let term = (msg) => {
        winston.info('container terminated:', msg.id);
        // this container died, unknown reason, decomision
        container.end(new Error(`container terminated`));

        if (this.__container === container) {
          delete this.__container;
        }
      }

      container.once('stop', () => {});
      container.once('die', term);

      this.__container = container;
    }

    return this.__container;
  }

  acquire(req) {
    let container = this.container();

    container.acquire(req);

    return container;
  }

  handle(req, res) {
    return req.__obj
            .acl()
            .then(() => this.acquire(req))
            .tap((container) => return container.wipe())
            .then((container) => {
              // todo [akamel] get oauth token, run private code
              let blob = container
                          .repository()
                          .then((repository) => {
                            let content   = req.__obj.doc.blob
                              , filename  = req.__obj.doc.filename
                              , branch    = req.__obj.doc.branch
                              , opts      = {
                                                branch    : branch
                                              , token     : undefined /*token*/
                                              , populate  : {
                                                  manual : true
                                              }
                                            }
                              ;

                            if (content) {
                              return repository.hotreload(filename, content, opts);
                            }

                            return repository.blob(filename, opts);
                          });

              Promise
                .all([
                    blob
                  , container.build()
                ])
                .spread((blob, build) => {
                  return container
                          .write(req.__obj.doc.filename, blob.content)
                          .then(() => {
                            console.log('as;diua8s', blob);
                            req.__obj.doc.manual = blob.manual;

                            delete req.__obj.doc.blob;
                          });
                })
                .then(() => container.await())
                .then(() => container.handle(req, res))
                .catch((err) => {
                  winston.error(err);
                  req.__obj.decline(err, req, res);
                });
            });
  }

  static get(req, res) {
    return Promise.try(() => {
      let can_cohost = req.__obj.canCoHost();

      // todo [akamel] branch should be part of the key because of package.json
      let key = can_cohost
                  ? req.__obj.remote
                  : `${req.__obj.remote}#${req.__obj.id}`;

      if (!store[key]) {
        store[key] = new Worker(key, req.__obj.remote, {
            'keep-alive'  : can_cohost
          , 'branch'      : req.__obj.branch
        });
      }

      return store[key];
    });
  }

  static handle(req, res) {
    req.__obj = new Request(req, res);

    return Worker
            .get(req, res)
            .then((worker) => worker.handle(req, res))
            .catch((err) => {
              winston.error(err);

              res.statusCode = 500;
              res.write(err.toString());
              res.end();
            });
  }

  static stats() {
    return _.map(store, (worker) => {
      return {
          id    : worker.id
        , stats : _.get(worker, '__container.stats')
      }
    });
  }
}

var store = {};

module.exports = Worker;<|endoftext|>


<commit_before>/*
 * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of fiware-iotagent-lib
 *
 * fiware-iotagent-lib is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * fiware-iotagent-lib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with fiware-iotagent-lib.
 * If not, seehttp://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::daniel.moranjimenez@telefonica.com
 */
'use strict';

/**
 * This module sets up the connection with the mongodb through mongoose. This connection will be used
 * in mongoose schemas to persist objects.
 */

var mongoose = require('mongoose'),
    config = require('../commonConfig'),
    constants = require('../constants'),
    alarms = require('../services/common/alarmManagement'),
    logger = require('logops'),
    async = require('async'),
    errors = require('../errors'),
    defaultDb,
    DEFAULT_DB_NAME = 'iotagent',
    context = {
        op: 'IoTAgentNGSI.DbConn'
    };

function loadModels() {
    require('./Device').load(defaultDb);
    require('./Group').load(defaultDb);
    require('./Command').load(defaultDb);
}

/**
 * Creates a new connection to the Mongo DB.
 *
 * @this Reference to the dbConn module itself.
 */
function init(host, db, port, username, password, options, callback) {
    /*jshint camelcase:false, validthis:true */
    var hosts,
        url,
        credentials = '',
        retries = 0,
        lastError,
        maxRetries = (config.getConfig().mongodb && config.getConfig().mongodb.retries ||
            constants.DEFAULT_MONGODB_RETRIES);

    if (username && password) {
        credentials = username + ':' + password + '@';
    }

    function addPort(item) {
        return item + ':' + port;
    }

    function commaConcat(previous, current, currentIndex) {
        if (currentIndex !== 0) {
            previous += ',';
        }

        previous += current;

        return previous;
    }

    hosts = host.split(',')
        .map(addPort)
        .reduce(commaConcat, '');

    url = 'mongodb://' + credentials + hosts + '/' + db;

    function createConnectionHandler(error, results) {
        if (defaultDb) {
            logger.info(context, 'Successfully connected to MongoDB.');
            module.exports.db = defaultDb;
            loadModels();
        } else {
            logger.error(context, 'MONGODB-002: Error found after [%d] attempts: %s', retries, error || lastError);
        }

        callback(error);
    }

    function retryCheck() {
        return !defaultDb && retries < maxRetries;
    }

    function connectionAttempt(url, options, callback) {
        var candidateDb;

        logger.info(context, 'Attempting to connect to MongoDB instance. Attempt %d', retries);

        candidateDb = mongoose.createConnection(url, options, function(error, result) {
            if (error) {
                logger.error(context, 'MONGODB-001: Error trying to connect to MongoDB: %s', error);
                lastError = error;
            } else {
                defaultDb = candidateDb;

                defaultDb.on('error', function(error) {
                    logger.error(context, 'Mongo Driver error: %j', error);
                    alarms.raise(constants.MONGO_ALARM, error);
                });
                defaultDb.on('connecting', function(error) {
                    logger.debug(context, 'Mongo Driver connecting');
                });
                defaultDb.on('connected', function() {
                    logger.debug(context, 'Mongo Driver connected');
                });
                defaultDb.on('reconnected', function() {
                    logger.debug(context, 'Mongo Driver reconnected');
                });
                defaultDb.on('disconnected', function() {
                    logger.debug(context, 'Mongo Driver disconnected');
                });
                defaultDb.on('disconnecting', function() {
                    logger.debug(context, 'Mongo Driver disconnecting');
                });
                defaultDb.on('open', function() {
                    logger.debug(context, 'Mongo Driver open');
                });
                defaultDb.on('close', function() {
                    logger.debug(context, 'Mongo Driver close');
                });
            }

            callback();
        });
    }

    function tryCreateConnection(callback) {
        var attempt = async.apply(connectionAttempt, url, options, callback),
            seconds = (config.getConfig().mongodb && config.getConfig().mongodb.retryTime ||
                constants.DEFAULT_MONGODB_RETRY_TIME);

        retries++;

        if (retries === 1) {
            logger.info(context, 'First connection attempt');
            attempt();
        } else {
            logger.info(context, 'Waiting %d seconds before attempting again.', seconds);
            setTimeout(attempt, seconds * 1000);
        }
    }

    defaultDb = null;
    async.whilst(retryCheck, tryCreateConnection, createConnectionHandler);
}

function configureDb(callback) {
    /*jshint camelcase:false, validthis:true */
    var currentConfig = config.getConfig();

    if ((currentConfig.deviceRegistry && currentConfig.deviceRegistry.type === 'mongodb') ||
        (currentConfig.stats && currentConfig.stats.persistence === true)) {
        if (!currentConfig.mongodb || !currentConfig.mongodb.host) {
            logger.fatal(context, 'MONGODB-003: No host found for MongoDB driver.');
            callback(new errors.BadConfiguration('No host found for MongoDB driver'));
        } else {
            var dbName = currentConfig.mongodb.db,
                port = currentConfig.mongodb.port || 27017,
                options = {};

            if (!currentConfig.mongodb.db) {
                dbName = DEFAULT_DB_NAME;
            }

            if (currentConfig.mongodb.replicaSet) {
                options.replset = { rs_name: currentConfig.mongodb.replicaSet };
            }

            init(config.getConfig().mongodb.host, dbName, port,
                currentConfig.username, currentConfig.password, {}, callback);
        }
    } else {
        callback();
    }
}

exports.configureDb = configureDb;
exports.db = defaultDb;
exports.DEFAULT_DB_NAME = DEFAULT_DB_NAME;
<commit_msg>add replicaSet in uri<commit_after>/*
 * Copyright 2014 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of fiware-iotagent-lib
 *
 * fiware-iotagent-lib is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * fiware-iotagent-lib is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with fiware-iotagent-lib.
 * If not, seehttp://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License
 * please contact with::daniel.moranjimenez@telefonica.com
 */
'use strict';

/**
 * This module sets up the connection with the mongodb through mongoose. This connection will be used
 * in mongoose schemas to persist objects.
 */

var mongoose = require('mongoose'),
    config = require('../commonConfig'),
    constants = require('../constants'),
    alarms = require('../services/common/alarmManagement'),
    logger = require('logops'),
    async = require('async'),
    errors = require('../errors'),
    defaultDb,
    DEFAULT_DB_NAME = 'iotagent',
    context = {
        op: 'IoTAgentNGSI.DbConn'
    };

function loadModels() {
    require('./Device').load(defaultDb);
    require('./Group').load(defaultDb);
    require('./Command').load(defaultDb);
}

/**
 * Creates a new connection to the Mongo DB.
 *
 * @this Reference to the dbConn module itself.
 */
function init(host, db, port, username, password, options, callback) {
    /*jshint camelcase:false, validthis:true */
    var hosts,
        url,
        credentials = '',
        retries = 0,
        lastError,
        maxRetries = (config.getConfig().mongodb && config.getConfig().mongodb.retries ||
            constants.DEFAULT_MONGODB_RETRIES);

    if (username && password) {
        credentials = username + ':' + password + '@';
    }

    function addPort(item) {
        return item + ':' + port;
    }

    function commaConcat(previous, current, currentIndex) {
        if (currentIndex !== 0) {
            previous += ',';
        }

        previous += current;

        return previous;
    }

    hosts = host.split(',')
        .map(addPort)
        .reduce(commaConcat, '');

    url = 'mongodb://' + credentials + hosts + '/' + db;

    if (options.replicaSet) {
        url += '?replicaSet=' + options.replicaSet.rs_name;
    }

    function createConnectionHandler(error, results) {
        if (defaultDb) {
            logger.info(context, 'Successfully connected to MongoDB.');
            module.exports.db = defaultDb;
            loadModels();
        } else {
            logger.error(context, 'MONGODB-002: Error found after [%d] attempts: %s', retries, error || lastError);
        }

        callback(error);
    }

    function retryCheck() {
        return !defaultDb && retries < maxRetries;
    }

    function connectionAttempt(url, options, callback) {
        var candidateDb;

        logger.info(context, 'Attempting to connect to MongoDB instance. Attempt %d', retries);

        candidateDb = mongoose.createConnection(url, options, function(error, result) {
            if (error) {
                logger.error(context, 'MONGODB-001: Error trying to connect to MongoDB: %s', error);
                lastError = error;
            } else {
                defaultDb = candidateDb;

                defaultDb.on('error', function(error) {
                    logger.error(context, 'Mongo Driver error: %j', error);
                    alarms.raise(constants.MONGO_ALARM, error);
                });
                defaultDb.on('connecting', function(error) {
                    logger.debug(context, 'Mongo Driver connecting');
                });
                defaultDb.on('connected', function() {
                    logger.debug(context, 'Mongo Driver connected');
                });
                defaultDb.on('reconnected', function() {
                    logger.debug(context, 'Mongo Driver reconnected');
                });
                defaultDb.on('disconnected', function() {
                    logger.debug(context, 'Mongo Driver disconnected');
                });
                defaultDb.on('disconnecting', function() {
                    logger.debug(context, 'Mongo Driver disconnecting');
                });
                defaultDb.on('open', function() {
                    logger.debug(context, 'Mongo Driver open');
                });
                defaultDb.on('close', function() {
                    logger.debug(context, 'Mongo Driver close');
                });
            }

            callback();
        });
    }

    function tryCreateConnection(callback) {
        var attempt = async.apply(connectionAttempt, url, options, callback),
            seconds = (config.getConfig().mongodb && config.getConfig().mongodb.retryTime ||
                constants.DEFAULT_MONGODB_RETRY_TIME);

        retries++;

        if (retries === 1) {
            logger.info(context, 'First connection attempt');
            attempt();
        } else {
            logger.info(context, 'Waiting %d seconds before attempting again.', seconds);
            setTimeout(attempt, seconds * 1000);
        }
    }

    defaultDb = null;
    async.whilst(retryCheck, tryCreateConnection, createConnectionHandler);
}

function configureDb(callback) {
    /*jshint camelcase:false, validthis:true */
    var currentConfig = config.getConfig();

    if ((currentConfig.deviceRegistry && currentConfig.deviceRegistry.type === 'mongodb') ||
        (currentConfig.stats && currentConfig.stats.persistence === true)) {
        if (!currentConfig.mongodb || !currentConfig.mongodb.host) {
            logger.fatal(context, 'MONGODB-003: No host found for MongoDB driver.');
            callback(new errors.BadConfiguration('No host found for MongoDB driver'));
        } else {
            var dbName = currentConfig.mongodb.db,
                port = currentConfig.mongodb.port || 27017,
                options = {};

            if (!currentConfig.mongodb.db) {
                dbName = DEFAULT_DB_NAME;
            }

            if (currentConfig.mongodb.replicaSet) {
                options.replset = { rs_name: currentConfig.mongodb.replicaSet };
            }

            init(config.getConfig().mongodb.host, dbName, port,
                currentConfig.username, currentConfig.password, {}, callback);
        }
    } else {
        callback();
    }
}

exports.configureDb = configureDb;
exports.db = defaultDb;
exports.DEFAULT_DB_NAME = DEFAULT_DB_NAME;
<|endoftext|>


<commit_before>// --------------------
// Sequelize hierarchy
// Extended model methods
// --------------------

// imports
var hooksModel = require('./hooksModel')

// exports

module.exports = function(Sequelize) {
	var Utils = Sequelize.Utils,
		_ = Utils._,
		hooks = hooksModel(Sequelize);

	// return extended methods
	return {
		isHierarchy: function(options) {
			// set up options
			if (!options) options = {};

			_.defaults(options, {
				as: 'parent',
				childrenAs: 'children',
				ancestorsAs: 'ancestors',
				descendentsAs: 'descendents',
				levelFieldName: 'hierarchyLevel',
				levelFieldType: Sequelize.INTEGER.UNSIGNED,
				onDelete: (options.onDelete || 'restrict').toLowerCase()
			});

			_.defaults(options, {
				foreignKey: options.as + Utils.uppercaseFirst(this.primaryKeyAttribute),
				throughKey: this.name + Utils.uppercaseFirst(this.primaryKeyAttribute),
				throughForeignKey: Utils.singularize(options.ancestorsAs, this.options.language) + Utils.uppercaseFirst(this.primaryKeyAttribute),
				through: Utils.pluralize(this.name, this.options.language) + Utils.uppercaseFirst(options.ancestorsAs)
			});

			if (['restrict', 'cascade'].indexOf(options.onDelete) == -1) throw new Error('onDelete on hierarchies must be either \'restrict\' or \'cascade\'');

			// record hierarchy in model
			this.hierarchy = options;

			// add level field to model
			this.rawAttributes[options.levelFieldName] = {type: options.levelFieldType};
			this.init(this.daoFactoryManager);

			// create associations
			this.hasMany(this, {as: options.childrenAs, foreignKey: options.foreignKey, onDelete: options.onDelete});
			this.belongsTo(this, {as: options.as, foreignKey: options.foreignKey});

			// create through table
			// NB this follows Sequelize's convention of naming the through model in plural,
			// even though inconsistent with usual Sequelize behaviour
			var sequelize = this.sequelize;

			var throughFields = {};
			throughFields[options.throughKey] = {type: this.rawAttributes[this.primaryKeyAttribute].type, allowNull: false, primaryKey: true};
			throughFields[options.throughForeignKey] = {type: this.rawAttributes[this.primaryKeyAttribute].type, allowNull: false, primaryKey: true};
			options.through = sequelize.define(options.through, throughFields, {timestamps: false, paranoid: false, tableName: options.through});

			// create associations through join table
			this.hasMany(this, {
				as: options.descendentsAs,
				foreignKey: options.throughForeignKey,
				through: options.through
			});

			this.hasMany(this, {
				as: options.ancestorsAs,
				foreignKey: options.throughKey,
				through: options.through
			});

			// apply hooks
			_.forIn(hooks, function(hookFn, hookName) {
				this.addHook(hookName, hookFn);
			}.bind(this));

			// return this for chaining
			return this;
		},

		rebuildHierarchy: function() {
			var model = this,
				hierarchy = this.hierarchy,
				primaryKey = this.primaryKeyAttribute,
				foreignKey = hierarchy.foreignKey,
				throughKey = hierarchy.throughKey,
				throughForeignKey = hierarchy.throughForeignKey,
				levelFieldName = hierarchy.levelFieldName,
				through = hierarchy.through;

			// truncate hierarchy through table
			return through.destroy({}, {truncate: true})
			.then(function() {
				// go up tree level by level and set hierarchy level field + create hierarchy through table records
				var updateAttr = {};
				updateAttr[levelFieldName] = 0;

				return (function processLevel(parents) {
					// find next level (i.e. children of last batch)
					var where = {};
					where[foreignKey] = parents ? parents.map(function(item) {return item.id;}) : null;

					return model.findAll({where: where, attributes: [primaryKey, foreignKey]}, {raw: true})
					.then(function(items) {
						if (!items.length) return;

						// update hierarchy level
						updateAttr[levelFieldName]++;

						return model.update(updateAttr, where)
						.then(function() {
							// add hierarchy path to newItems from items + create array for inserting into through table
							var ancestors = [];

							items = items.map(function(item) {
								var itemId = item[primaryKey];
								var parentId = item[foreignKey];
								var parent = parentId !== null ? _.find(parents, {id: parentId}) : {path: []};

								item = {id: itemId, path: _.clone(parent.path)};

								item.path.forEach(function(ancestorId) {
									var ancestor = {};
									ancestor[throughKey] = itemId;
									ancestor[throughForeignKey] = ancestorId;

									ancestors.push(ancestor);
								});

								item.path.push(itemId);

								return item;
							});

							// insert rows into hierarchy through table
							return through.bulkCreate(ancestors)
							.then(function() {
								return processLevel(items);
							});
						});
					});
				})();
			})
			// return this for chaining
			.return(this);
		}
	};
};
<commit_msg>rebuildHierarchy bug fix<commit_after>// --------------------
// Sequelize hierarchy
// Extended model methods
// --------------------

// imports
var hooksModel = require('./hooksModel')

// exports

module.exports = function(Sequelize) {
	var Utils = Sequelize.Utils,
		_ = Utils._,
		hooks = hooksModel(Sequelize);

	// return extended methods
	return {
		isHierarchy: function(options) {
			// set up options
			if (!options) options = {};

			_.defaults(options, {
				as: 'parent',
				childrenAs: 'children',
				ancestorsAs: 'ancestors',
				descendentsAs: 'descendents',
				levelFieldName: 'hierarchyLevel',
				levelFieldType: Sequelize.INTEGER.UNSIGNED,
				onDelete: (options.onDelete || 'restrict').toLowerCase()
			});

			_.defaults(options, {
				foreignKey: options.as + Utils.uppercaseFirst(this.primaryKeyAttribute),
				throughKey: this.name + Utils.uppercaseFirst(this.primaryKeyAttribute),
				throughForeignKey: Utils.singularize(options.ancestorsAs, this.options.language) + Utils.uppercaseFirst(this.primaryKeyAttribute),
				through: Utils.pluralize(this.name, this.options.language) + Utils.uppercaseFirst(options.ancestorsAs)
			});

			if (['restrict', 'cascade'].indexOf(options.onDelete) == -1) throw new Error('onDelete on hierarchies must be either \'restrict\' or \'cascade\'');

			// record hierarchy in model
			this.hierarchy = options;

			// add level field to model
			this.rawAttributes[options.levelFieldName] = {type: options.levelFieldType};
			this.init(this.daoFactoryManager);

			// create associations
			this.hasMany(this, {as: options.childrenAs, foreignKey: options.foreignKey, onDelete: options.onDelete});
			this.belongsTo(this, {as: options.as, foreignKey: options.foreignKey});

			// create through table
			// NB this follows Sequelize's convention of naming the through model in plural,
			// even though inconsistent with usual Sequelize behaviour
			var sequelize = this.sequelize;

			var throughFields = {};
			throughFields[options.throughKey] = {type: this.rawAttributes[this.primaryKeyAttribute].type, allowNull: false, primaryKey: true};
			throughFields[options.throughForeignKey] = {type: this.rawAttributes[this.primaryKeyAttribute].type, allowNull: false, primaryKey: true};
			options.through = sequelize.define(options.through, throughFields, {timestamps: false, paranoid: false, tableName: options.through});

			// create associations through join table
			this.hasMany(this, {
				as: options.descendentsAs,
				foreignKey: options.throughForeignKey,
				through: options.through
			});

			this.hasMany(this, {
				as: options.ancestorsAs,
				foreignKey: options.throughKey,
				through: options.through
			});

			// apply hooks
			_.forIn(hooks, function(hookFn, hookName) {
				this.addHook(hookName, hookFn);
			}.bind(this));

			// return this for chaining
			return this;
		},

		rebuildHierarchy: function() {
			var model = this,
				hierarchy = this.hierarchy,
				primaryKey = this.primaryKeyAttribute,
				foreignKey = hierarchy.foreignKey,
				throughKey = hierarchy.throughKey,
				throughForeignKey = hierarchy.throughForeignKey,
				levelFieldName = hierarchy.levelFieldName,
				through = hierarchy.through;

			// truncate hierarchy through table
			return through.destroy({}, {truncate: true})
			.then(function() {
				// go up tree level by level and set hierarchy level field + create hierarchy through table records
				var updateAttr = {};
				updateAttr[levelFieldName] = 0;

				return (function processLevel(parents) {
					// find next level (i.e. children of last batch)
					var where = {};
					where[foreignKey] = parents ? parents.map(function(item) {return item.id;}) : null;

					return model.findAll({where: where, attributes: [primaryKey, foreignKey]}, {raw: true})
					.then(function(items) {
						if (!items.length) return;

						// update hierarchy level
						updateAttr[levelFieldName]++;

						return model.update(updateAttr, {where: where})
						.then(function() {
							// add hierarchy path to newItems from items + create array for inserting into through table
							var ancestors = [];

							items = items.map(function(item) {
								var itemId = item[primaryKey];
								var parentId = item[foreignKey];
								var parent = parentId !== null ? _.find(parents, {id: parentId}) : {path: []};

								item = {id: itemId, path: _.clone(parent.path)};

								item.path.forEach(function(ancestorId) {
									var ancestor = {};
									ancestor[throughKey] = itemId;
									ancestor[throughForeignKey] = ancestorId;

									ancestors.push(ancestor);
								});

								item.path.push(itemId);

								return item;
							});

							// insert rows into hierarchy through table
							return through.bulkCreate(ancestors)
							.then(function() {
								return processLevel(items);
							});
						});
					});
				})();
			})
			// return this for chaining
			.return(this);
		}
	};
};
<|endoftext|>