<commit_before>// This library requires mathtools.js to work

function ParameterClass(ParameterID, GroupName){// InterfacePrefix

this.ParameterID=ParameterID;// e.g. LFHCCProbability
this.InterfaceID="";// e.g. HCV_LFHCCProbability
// Note that GroupName is primarily used for interface design. It is updated at load to represent the true structure of the objects in which it resides, so it cannot be relied upon to be consistent between builds.
this.GroupName="";//e.g. HCV, or in the case of it being a struct of a struct, Param.HCV
//GroupName+'.'+ParameterID gives the full name that the function can call.

this.DistributionType="uniform";
//0: uniform
//1: normal
//2: lognormal
//3: optimisedsample //used in a case where the parameters is unknown and an algorithm decides the sample chosen based on the outcome of an optimisation routine

// The following are used for inside the simulations
//4: exponential
//5: logitnormal   // used for probability distributions (on [0,1]). Note sigma<1 to avoid strange edge effects
//6: poisson
//7: sqrnormal

this.Median=NaN;
this.StandardError=NaN;
this.LowerBound=NaN;//used for uniform distributions
this.UpperBound=NaN;//used for uniform distributions

this.Upper95Range=NaN;// These values are calculated on the fly by the program, and are displayed in the interface
this.Lower95Range=NaN;

this.NumberOfSamples=NaN;
this.Val=[];//This is the array of the samples

this.MinValue="";// These values are used to put a bound on the values, e.g. many values must not be less than zero.
this.MaxValue="";

//The Description is used to describe what the variables are and where they are from including URLs and any other calculation
this.Description=[];//
}

//Load the parameter from text
	//or maybe simply bind a JSON object
ParameterClass.prototype.Load= function (InputStructure){
	for (var Key in this){
		if (typeof(InputStructure[Key])!='undefined'){
			this[key]=InputStructure[Key];
		}
	}
}

ParameterClass.prototype.Name= function (){
	return (this.GroupName+'.'+this.ParameterID);
}

ParameterClass.prototype.UpdateTypeDisplay= function (){
	// Set up the name and parameter type
	var ParameterHTML=""+
	"<input type='text' name='ParameterID' value='" + this.ParameterID + "'>\n"+
	"<select name='DistributionType'  onchange='"+this.Name()+".DistributionType=this.value;"+this.Name()+".UpdateTypeDisplay();'>\n"+//change the type, then redisplay

	"	<option value='normal'>Normal</option>\n"+
	"	<option value='lognormal'>Log Normal</option>\n"+
	"	<option value='uniform'>Uniform</option>\n"+
	"	<option value='optimisedsample'>Optimised Sample</option>\n"+

	"</select>\n ";

	if (this.DistributionType=="normal"){
		ParameterHTML+="Median <input type='text' name='Median' value='" + this.Median + " '>\n"+
		" Standard error <input type='text' name='StandardError' value='" + this.StandardError + " '>\n";
	}
	else if (this.DistributionType=="lognormal"){
		ParameterHTML+="Median <input type='text' name='Median' value='" + this.Median + " '>\n"+
		" Standard error of log <input type='text' name='StandardError' value='" + this.StandardError + " '>\n";
	}
	else if (this.DistributionType=="uniform"){
		ParameterHTML+="Lower Bound <input type='text' name='LowerBound' value='" + this.LowerBound + " '>\n"+
		" Upper Bound <input type='text' name='UpperBound' value='" + this.UpperBound + " '>\n";
	}
	else if (this.DistributionType=="optimisedsample"){
		ParameterHTML+="Lower Bound <input type='text' name='LowerBound' value='" + this.LowerBound + " '>\n"+
		" Upper Bound <input type='text' name='UpperBound' value='" + this.UpperBound + " '>\n";
	}

	// onchange="Settings.NumberOfSimulations=Number(this.value);SaveSettings();"
	// onchange="ID.Change(this.Name, this.value)";
	// in the function Number(this.value); SaveParameterFile();

	//
	//
	//
	//
	//		Median <input type="text" name="Median">
	//		SD <input type="text" name="SD">
	//		+ More info
	//
 Some info to display

	//

	// Determine estimated value of the mean, IQR and 95% range of the parameters
	this.CalculateUncertaintyBounds();
	// Display this information to the user

	// Save button
		// When pressed,
		// Get the values from all the fields
		// save the value to Param
		// Save Param

	// This information exists for all parameters
	ParameterHTML=ParameterHTML+
		" Save  \n"+
		" + More info \n"+
		"
\n"+
		"
\n"+
		"    Median <input type='text' name='' value='" + this.Median + " '>\n"+
		"    Lower 95%CI <input type='text' name='' value='" + this.Lower95Range + " '>\n"+
		"    Upper 95%CI <input type='text' name='' value='" + this.Upper95Range + " '>\n"+
		"
Description
"+
		"    <textarea name='Description' cols='100' rows='5'>"+this.Description+"</textarea>";
	//For each of the references, add a link, a box for the title, and the link
	//
	//Add a "add ref button"
	//

	//Close off the references div
	ParameterHTML=ParameterHTML+"
";

	// Set the parameter
	document.getElementById(this.InterfaceID).innerHTML=ParameterHTML;
	document.getElementById(this.InterfaceID).DistributionType.value=this.DistributionType;
}

ParameterClass.prototype.Save=function(){
	console.log("Button pressing works");

	// Go through each of the possible elements
	var FieldNames=['ParameterID',
					'InterfaceID',
					'DistributionType',
					'Median',
					'StandardError',
					'LowerBound',
					'UpperBound',
					'Upper95Range',
					'Lower95Range',
					'MinValue',
					'MaxValue',
					'Description'];
	// See if it exists
	var ParamElement = document.getElementById(this.InterfaceID);
	// if it exists, set to the value in the Param
	for (var Field in FieldNames){
		console.log(FieldNames[Field]);
		if (typeof(ParamElement[FieldNames[Field]])!='undefined'){
			this[FieldNames[Field]]=ParamElement[FieldNames[Field]].value;

			console.log(this[FieldNames[Field]]);
		}

	}

	console.error('Need to convert to numbers before this will work. Spaces making your life tough');

	// if the name in the display is different to the name in the

	// if the name changes, update the InterfaceID
	var OldId=this.InterfaceID;
	// DetermineInterfaceID(this.Name);

	this.UpdateTypeDisplay();
}

//Load the parameter from interface
	//This requires that the
//Save this interface as

ParameterClass.prototype.CalculateUncertaintyBounds= function (){
	if (this.DistributionType=="normal"){
		this.Upper95Range=this.Median+1.96*this.StandardError;
		this.Lower95Range=this.Median-1.96*this.StandardError;
	}
	else if (this.DistributionType=="lognormal"){
		//log
		var LogMean=Log(this.Median);
		//calculate //delog
		this.Upper95Range=Exp(LogMean+1.96*this.StandardError);
		this.Lower95Range=Exp(LogMean-1.96*this.StandardError);
	}
}

ParameterClass.prototype.CreateDistribution= function (){

	if (this.distributionType=="lognormal"){
		//log
		var logmedian=Math.log(this.Median);
		//calculate
		Rand.Value();
		//some code here https://gist.github.com/Protonk/5367430
		//Linear congruential generator

		//http://stackoverflow.com/questions/20160827/when-generating-normally-distributed-random-values-what-is-the-most-efficient-w
		// To get approximately normally distributed random numbers, add 12 rands, minus 6, divide
		//

		//delog
	}

}

//**************************************************************************************************************************
// From this point, the code is no longer dealing with individual parameters, but with displaying groups of parameters in a page

function BuildParameterPage(ParamGroup, ParamGroupDivId, GroupName){

	//Firstly, ensure that each of the parameters in the group have the proper GroupName e.g. Param.HIV
	//This allows the object to be able to write code that allows it to execute itself, e.g. Param.HIV.CalculateAndDisplayMedian()
	for (var key in ParamGroup) {// Code inspired by http://stackoverflow.com/questions/208016/how-to-list-the-properties-of-a-javascript-object
		if (ParamGroup.hasOwnProperty(key)) {
			ParamGroup[key].GroupName=GroupName;
		}
	}

	var BuildText="";
	for (var key in ParamGroup) {// Code inspired by http://stackoverflow.com/questions/208016/how-to-list-the-properties-of-a-javascript-object
		if (ParamGroup.hasOwnProperty(key)) {
			//maybe also check here that the parameter is not an array, other wise use a different function
			ParamGroup[key].InterfaceID=ParamGroupDivId+key;
			BuildText=BuildText+"                <form class=\"ParamContainer\" id=\""+ParamGroup[key].InterfaceID+"\"></form>\n";
		}
	}

	//This next section is very naughty, because it breaks the div by setting the inner HTML. It will probably break.
	BuildText=BuildText+"
Add parameter
"
	BuildText=BuildText+"
Unsaved parameters <\p>";
	document.getElementById(ParamGroupDivId).innerHTML = BuildText;

	//Load each of the holders with the parameters that should be held
	for (var key in ParamGroup) {
		if (ParamGroup.hasOwnProperty(key)) {
			//Update display of parameter
			ParamGroup[key].UpdateTypeDisplay();
		}
	}
}

function AddNewParameter(ParamGroup, GroupName, ParamGroupDivId){
	//Count number of current
	var NumParams=1;
	for (var key in ParamGroup) {
		NumParams++;
	}
	var ParamName="NewParam"+NumParams;//The new parameter will be called e.g. NewParam12
	ParamGroup[ParamName]=new ParameterClass(ParamName);
	// Add the GroupName
	ParamGroup[ParamName].GroupName=GroupName;

	// Name the interface
	ParamGroup[ParamName].InterfaceID=ParamGroupDivId+ParamName;
	HTMLToAdd="\n                <form class=\"ParamContainer\" id=\""+ParamGroup[ParamName].InterfaceID+"\"></form>";
	document.getElementById(ParamGroupDivId).innerHTML = document.getElementById(ParamGroupDivId).innerHTML + HTMLToAdd;

	console.log(ParamGroup[ParamName].Name());
	console.log(ParamGroup[ParamName].GroupName);

	ParamGroup[ParamName].UpdateTypeDisplay();
}

//   create a new 'form' element

// Renaming a key for an object
//if (old_key !== new_key) {
//    Object.defineProperty(o, new_key, Object.getOwnPropertyDescriptor(o, old_key));
//    delete o[old_key];
//}

// Load parameters from a file
function LoadParameters(ParameterFileName){
	// Open the file
	var ParamStruct=fs.readFileSync("./model/parameters.json", 'utf8');
	console.log(ParamStruct);

	console.error("Note that file error handling has not yet been implemented. Maybe use a try/catch system. Determine how to use readFileSync properly to ensure that the lack of the file is handled properly.");
	var ParamStruct=fs.readFileSync("./model/parameters.json", 'utf8', function (err, data) {
		console.log("1");
		if (err){
			console.error("The file " + ParameterFileName+" could not be loaded. Error: ");
			console.error(err);
			var ParsedStruct={};
			ParsedStruct.ParamLoadingError=err;
			return ParsedStruct;
		}
		else{
			// Parse the JSON
			var ParsedStruct= JSON.parse(data);
			return ParsedStruct;
		}
	});
	console.log(ParamStruct);
	// Check for error
	if (typeof(ParamStruct.ParamLoadingError)!='undefined'){
		return ParamStruct;
	}

	// Check for problems

	var Param={};
	// Parse into each subgroup
	for (var Key in ParamStruct){
		// Create a new parameter for each value in the group
		Param[Key]={};
		for (var Key2 in ParamStruct[Key]){
			Param[Key][Key2]=new ParameterClass(Key2);
			Param[Key][Key2].Load(ParamStruct[Key][Key2]);
		}
	}
	return Param;
}

// Save parameters to file
<commit_msg>Converting to numbers, adding uniform distributions<commit_after>// This library requires mathtools.js to work

function ParameterClass(ParameterID, GroupName){// InterfacePrefix

this.ParameterID=ParameterID;// e.g. LFHCCProbability
this.InterfaceID="";// e.g. HCV_LFHCCProbability
// Note that GroupName is primarily used for interface design. It is updated at load to represent the true structure of the objects in which it resides, so it cannot be relied upon to be consistent between builds.
this.GroupName="";//e.g. HCV, or in the case of it being a struct of a struct, Param.HCV
//GroupName+'.'+ParameterID gives the full name that the function can call.

this.DistributionType="uniform";
//0: uniform
//1: normal
//2: lognormal
//3: optimisedsample //used in a case where the parameters is unknown and an algorithm decides the sample chosen based on the outcome of an optimisation routine

// The following are used for inside the simulations
//4: exponential
//5: logitnormal   // used for probability distributions (on [0,1]). Note sigma<1 to avoid strange edge effects
//6: poisson
//7: sqrnormal

this.Median=NaN;
this.StandardError=NaN;
this.LowerBound=NaN;//used for uniform distributions
this.UpperBound=NaN;//used for uniform distributions

this.Upper95Range=NaN;// These values are calculated on the fly by the program, and are displayed in the interface
this.Lower95Range=NaN;
this.CalculatedMedian=NaN;

this.NumberOfSamples=NaN;
this.Val=[];//This is the array of the samples

this.MinValue="";// These values are used to put a bound on the values, e.g. many values must not be less than zero.
this.MaxValue="";

//The Description is used to describe what the variables are and where they are from including URLs and any other calculation
this.Description=[];//
}

//Load the parameter from text
	//or maybe simply bind a JSON object
ParameterClass.prototype.Load= function (InputStructure){
	for (var Key in this){
		if (typeof(InputStructure[Key])!='undefined'){
			this[key]=InputStructure[Key];
		}
	}
}

ParameterClass.prototype.Name= function (){
	return (this.GroupName+'.'+this.ParameterID);
}

ParameterClass.prototype.UpdateTypeDisplay= function (){
	// Set up the name and parameter type
	var ParameterHTML=""+
	"<input type='text' name='ParameterID' value='" + this.ParameterID + "'>\n"+
	"<select name='DistributionType'  onchange='"+this.Name()+".DistributionType=this.value;"+this.Name()+".UpdateTypeDisplay();'>\n"+//change the type, then redisplay

	"	<option value='normal'>Normal</option>\n"+
	"	<option value='lognormal'>Log Normal</option>\n"+
	"	<option value='uniform'>Uniform</option>\n"+
	"	<option value='optimisedsample'>Optimised Sample</option>\n"+

	"</select>\n ";

	if (this.DistributionType=="normal"){
		ParameterHTML+="Median <input type='text' name='Median' value='" + this.Median + " '>\n"+
		" Standard error <input type='text' name='StandardError' value='" + this.StandardError + " '>\n";
	}
	else if (this.DistributionType=="lognormal"){
		ParameterHTML+="Median <input type='text' name='Median' value='" + this.Median + " '>\n"+
		" Standard error of log <input type='text' name='StandardError' value='" + this.StandardError + " '>\n";
	}
	else if (this.DistributionType=="uniform"){
		ParameterHTML+="Lower Bound <input type='text' name='LowerBound' value='" + this.LowerBound + " '>\n"+
		" Upper Bound <input type='text' name='UpperBound' value='" + this.UpperBound + " '>\n";
	}
	else if (this.DistributionType=="optimisedsample"){
		ParameterHTML+="Lower Bound <input type='text' name='LowerBound' value='" + this.LowerBound + " '>\n"+
		" Upper Bound <input type='text' name='UpperBound' value='" + this.UpperBound + " '>\n";
	}

	// onchange="Settings.NumberOfSimulations=Number(this.value);SaveSettings();"
	// onchange="ID.Change(this.Name, this.value)";
	// in the function Number(this.value); SaveParameterFile();

	//
	//
	//
	//
	//		Median <input type="text" name="Median">
	//		SD <input type="text" name="SD">
	//		+ More info
	//
 Some info to display

	//

	// Determine estimated value of the mean, IQR and 95% range of the parameters
	this.CalculateUncertaintyBounds();
	// Display this information to the user

	// Save button
		// When pressed,
		// Get the values from all the fields
		// save the value to Param
		// Save Param

	// This information exists for all parameters
	ParameterHTML=ParameterHTML+
		" Save  \n"+
		" + More info \n"+
		"
\n"+
		"
\n"+
		"    Median <input type='text' name='' value='" + this.CalculatedMedian + " '>\n"+
		"    Lower 95%CI <input type='text' name='' value='" + this.Lower95Range + " '>\n"+
		"    Upper 95%CI <input type='text' name='' value='" + this.Upper95Range + " '>\n"+
		"
Description
"+
		"    <textarea name='Description' cols='100' rows='5'>"+this.Description+"</textarea>";
	//For each of the references, add a link, a box for the title, and the link
	//
	//Add a "add ref button"
	//

	//Close off the references div
	ParameterHTML=ParameterHTML+"
";

	// Set the parameter
	document.getElementById(this.InterfaceID).innerHTML=ParameterHTML;
	document.getElementById(this.InterfaceID).DistributionType.value=this.DistributionType;
}

ParameterClass.prototype.Save=function(){
	console.log("Button pressing works");

	// Go through each of the possible elements
	var FieldsList=[];

	var FieldNames=['ParameterID',
					'DistributionType',
					'Median',
					'StandardError',
					'LowerBound',
					'UpperBound',
					'Upper95Range',
					'Lower95Range',
					'MinValue',
					'MaxValue',
					'Description'];

	var FieldTypes=['text',
					'text',
					'number',
					'number',
					'number',
					'number',
					'number',
					'number',
					'number',
					'number',
					'text'];

	// See if it exists
	var ParamElement = document.getElementById(this.InterfaceID);
	// if it exists, set to the value in the Param
	for (var Field in FieldNames){
		console.log(FieldNames[Field]);
		if (typeof(ParamElement[FieldNames[Field]])!='undefined'){
			if (FieldTypes[Field]=='number'){
				this[FieldNames[Field]]=Number(ParamElement[FieldNames[Field]].value);
			}
			else{
				this[FieldNames[Field]]=ParamElement[FieldNames[Field]].value;
			}
			console.log(this[FieldNames[Field]]);
		}

	}

	console.error('Need to convert to numbers before this will work. Spaces making your life tough');

	// if the name in the display is different to the name in the

	// if the name changes, update the InterfaceID
	var OldId=this.InterfaceID;
	// DetermineInterfaceID(this.Name);

	this.UpdateTypeDisplay();
}

//Load the parameter from interface
	//This requires that the
//Save this interface as

ParameterClass.prototype.CalculateUncertaintyBounds= function (){
	if (this.DistributionType=="normal"){
		this.CalculatedMedian=this.Median;
		this.Upper95Range=this.Median+1.96*this.StandardError;
		this.Lower95Range=this.Median-1.96*this.StandardError;
	}
	else if (this.DistributionType=="lognormal"){
		this.CalculatedMedian=this.Median;
		//log
		var LogMean=Log(this.Median);
		//calculate //delog
		this.Upper95Range=Exp(LogMean+1.96*this.StandardError);
		this.Lower95Range=Exp(LogMean-1.96*this.StandardError);
	}
	else if(this.DistributionType=="uniform"){
		this.CalculatedMedian=(this.LowerBound+this.UpperBound)/2;
		this.Lower95Range=this.LowerBound+0.05*(this.UpperBound-this.LowerBound);
		this.Upper95Range=this.LowerBound+0.95*(this.UpperBound-this.LowerBound);
	}
	else if(this.DistributionType=="optimisedsample"){
		if (this.Val.length>0){
			this.CalculatedMedian=Median(this.Val);
			this.Lower95Range=Percentile(this.Val, 5);
			this.Upper95Range=Percentile(this.Val, 95);
		}
	}
}

ParameterClass.prototype.CreateDistribution= function (){

	if (this.distributionType=="lognormal"){
		//log
		var logmedian=Math.log(this.Median);
		//calculate
		Rand.Value();
		//some code here https://gist.github.com/Protonk/5367430
		//Linear congruential generator

		//http://stackoverflow.com/questions/20160827/when-generating-normally-distributed-random-values-what-is-the-most-efficient-w
		// To get approximately normally distributed random numbers, add 12 rands, minus 6, divide
		//

		//delog
	}

}

//**************************************************************************************************************************
// From this point, the code is no longer dealing with individual parameters, but with displaying groups of parameters in a page

function BuildParameterPage(ParamGroup, ParamGroupDivId, GroupName){

	//Firstly, ensure that each of the parameters in the group have the proper GroupName e.g. Param.HIV
	//This allows the object to be able to write code that allows it to execute itself, e.g. Param.HIV.CalculateAndDisplayMedian()
	for (var key in ParamGroup) {// Code inspired by http://stackoverflow.com/questions/208016/how-to-list-the-properties-of-a-javascript-object
		if (ParamGroup.hasOwnProperty(key)) {
			ParamGroup[key].GroupName=GroupName;
		}
	}

	var BuildText="";
	for (var key in ParamGroup) {// Code inspired by http://stackoverflow.com/questions/208016/how-to-list-the-properties-of-a-javascript-object
		if (ParamGroup.hasOwnProperty(key)) {
			//maybe also check here that the parameter is not an array, other wise use a different function
			ParamGroup[key].InterfaceID=ParamGroupDivId+key;
			BuildText=BuildText+"                <form class=\"ParamContainer\" id=\""+ParamGroup[key].InterfaceID+"\"></form>\n";
		}
	}

	//This next section is very naughty, because it breaks the div by setting the inner HTML. It will probably break.
	BuildText=BuildText+"
Add parameter
"
	BuildText=BuildText+"
Unsaved parameters <\p>";
	document.getElementById(ParamGroupDivId).innerHTML = BuildText;

	//Load each of the holders with the parameters that should be held
	for (var key in ParamGroup) {
		if (ParamGroup.hasOwnProperty(key)) {
			//Update display of parameter
			ParamGroup[key].UpdateTypeDisplay();
		}
	}
}

function AddNewParameter(ParamGroup, GroupName, ParamGroupDivId){
	//Count number of current
	var NumParams=1;
	for (var key in ParamGroup) {
		NumParams++;
	}
	var ParamName="NewParam"+NumParams;//The new parameter will be called e.g. NewParam12
	ParamGroup[ParamName]=new ParameterClass(ParamName);
	// Add the GroupName
	ParamGroup[ParamName].GroupName=GroupName;

	// Name the interface
	ParamGroup[ParamName].InterfaceID=ParamGroupDivId+ParamName;
	HTMLToAdd="\n                <form class=\"ParamContainer\" id=\""+ParamGroup[ParamName].InterfaceID+"\"></form>";
	document.getElementById(ParamGroupDivId).innerHTML = document.getElementById(ParamGroupDivId).innerHTML + HTMLToAdd;

	console.log(ParamGroup[ParamName].Name());
	console.log(ParamGroup[ParamName].GroupName);

	ParamGroup[ParamName].UpdateTypeDisplay();
}

//   create a new 'form' element

// Renaming a key for an object
//if (old_key !== new_key) {
//    Object.defineProperty(o, new_key, Object.getOwnPropertyDescriptor(o, old_key));
//    delete o[old_key];
//}

// Load parameters from a file
function LoadParameters(ParameterFileName){
	// Open the file
	var ParamStruct=fs.readFileSync("./model/parameters.json", 'utf8');
	console.log(ParamStruct);

	console.error("Note that file error handling has not yet been implemented. Maybe use a try/catch system. Determine how to use readFileSync properly to ensure that the lack of the file is handled properly.");
	var ParamStruct=fs.readFileSync("./model/parameters.json", 'utf8', function (err, data) {
		console.log("1");
		if (err){
			console.error("The file " + ParameterFileName+" could not be loaded. Error: ");
			console.error(err);
			var ParsedStruct={};
			ParsedStruct.ParamLoadingError=err;
			return ParsedStruct;
		}
		else{
			// Parse the JSON
			var ParsedStruct= JSON.parse(data);
			return ParsedStruct;
		}
	});
	console.log(ParamStruct);
	// Check for error
	if (typeof(ParamStruct.ParamLoadingError)!='undefined'){
		return ParamStruct;
	}

	// Check for problems

	var Param={};
	// Parse into each subgroup
	for (var Key in ParamStruct){
		// Create a new parameter for each value in the group
		Param[Key]={};
		for (var Key2 in ParamStruct[Key]){
			Param[Key][Key2]=new ParameterClass(Key2);
			Param[Key][Key2].Load(ParamStruct[Key][Key2]);
		}
	}
	return Param;
}

// Save parameters to file
<|endoftext|>


<commit_before>const Sequelize = require('sequelize');
const ApplicationRecord = require('./application-record');

module.exports = ApplicationRecord.define('bookmark', {
  repoId: Sequelize.INTEGER,
  fullName: Sequelize.STRING,
}, {});
<commit_msg>- Undersorelize field name to fit github style API. - Mark repo_id as unique field.<commit_after>const Sequelize = require('sequelize');
const ApplicationRecord = require('./application-record');

module.exports = ApplicationRecord.define('bookmark', {
  repo_id: {
    type: Sequelize.INTEGER,
    unique: true,
  },
  full_name: Sequelize.STRING,
}, {});
<|endoftext|>


<commit_before>var _       = require("underscore")._,
    async   = require("async"),
    fs      = require("fs"),
    exec    = require("child_process").exec,
    iwlist     = require("./iwlist"),
    config  = require("../config.json");
var scanResult;

// Better template format
_.templateSettings = {
    interpolate: /\{\{(.+?)\}\}/g,
    evaluate :   /\{\[([\s\S]+?)\]\}/g
};

// Helper function to write a given template to a file based on a given
// context
function write_template_to_file(template_path, file_name, context, callback) {
    async.waterfall([

        function read_template_file(next_step) {
            fs.readFile(template_path, {encoding: "utf8"}, next_step);
        },

        function update_file(file_txt, next_step) {
            var template = _.template(file_txt);
            fs.writeFile(file_name, template(context), next_step);
        }

    ], callback);
}

/*****************************************************************************\
    Return a set of functions which we can use to manage and check our wifi
    connection information
\*****************************************************************************/
module.exports = function() {
    // Detect which wifi driver we should use, the rtl871xdrv or the nl80211
    exec("iw list", function(error, stdout, stderr) {
        if (stderr.match(/^nl80211 not found/)) {
            config.wifi_driver_type = "rtl871xdrv";
        }
        // console.log("config.wifi_driver_type = " + config.wifi_driver_type);
    });

    // Hack: this just assumes that the outbound interface will be "wlan0"

    // Define some globals
    var ifconfig_fields = {
        "hw_addr":         /HWaddr\s([^\s]+)/,
        "inet_addr":       /inet addr:([^\s]+)/,
    },  iwconfig_fields = {
        "ap_addr":         /Access Point:\s([^\s]+)/,
        "ap_ssid":         /ESSID:\"([^\"]+)\"/,
        "unassociated":    /(unassociated)\s+Nick/,
    },  last_wifi_info = null;

    // TODO: rpi-config-ap hardcoded, should derive from a constant

    // Get generic info on an interface
    var _get_wifi_info = function(callback) {
        var output = {
            hw_addr:      "<unknown>",
            inet_addr:    "<unknown>",
            unassociated: "<unknown>",
        };

        // Inner function which runs a given command and sets a bunch
        // of fields
        function run_command_and_set_fields(cmd, fields, callback) {
            exec(cmd, function(error, stdout, stderr) {
                if (error) return callback(error);
                for (var key in fields) {
                    re = stdout.match(fields[key]);
                    if (re && re.length > 1) {
                        output[key] = re[1];
                    }
                }
                callback(null);
            });
        }

        // Run a bunch of commands and aggregate info
        async.series([
            function run_ifconfig(next_step) {
                run_command_and_set_fields("ifconfig wlan0", ifconfig_fields, next_step);
            },
            function run_iwconfig(next_step) {
                run_command_and_set_fields("iwconfig wlan0", iwconfig_fields, next_step);
            },
        ], function(error) {
            last_wifi_info = output;
            return callback(error, output);
        });
    },

    _reboot_wireless_network = function(wlan_iface, callback) {
        async.series([
            function down(next_step) {
                exec("sudo ifdown " + wlan_iface, function(error, stdout, stderr) {
                    if (!error) console.log("ifdown " + wlan_iface + " successful...");
                    next_step();
                });
            },
            function up(next_step) {
                exec("sudo ifup " + wlan_iface, function(error, stdout, stderr) {
                    if (!error) console.log("ifup " + wlan_iface + " successful...");
                    next_step();
                });
            },
        ], callback);
    },

    // Wifi related functions
    _is_wifi_enabled_sync = function(info) {
        // If we are not an AP, and we have a valid
        // inet_addr - wifi is enabled!
        if (null        == _is_ap_enabled_sync(info) &&
            "<unknown>" != info["inet_addr"]         &&
            "<unknown>" == info["unassociated"] ) {
            return info["inet_addr"];
        }
        return null;
    },

    _is_wifi_enabled = function(callback) {
        _get_wifi_info(function(error, info) {
            if (error) return callback(error, null);
            return callback(null, _is_wifi_enabled_sync(info));
        });
    },

    // Access Point related functions
    _is_ap_enabled_sync = function(info) {
        // If the hw_addr matches the ap_addr
        // and the ap_ssid matches "rpi-config-ap"
        // then we are in AP mode
        var is_ap  =
            info["hw_addr"].toLowerCase() == info["ap_addr"].toLowerCase() &&
            info["ap_ssid"] == config.access_point.ssid;
        return (is_ap) ? info["hw_addr"].toLowerCase() : null;
    },

    _is_ap_enabled = function(callback) {
        _get_wifi_info(function(error, info) {
            if (error) return callback(error, null);
            return callback(null, _is_ap_enabled_sync(info));
        });
    },

    // Enables the accesspoint w/ bcast_ssid. This assumes that both
    // isc-dhcp-server and hostapd are installed using:
    // $sudo npm run-script provision
    _enable_ap_mode = function(bcast_ssid, callback) {
        _is_ap_enabled(function(error, result_addr) {
            if (error) {
                console.log("ERROR: " + error);
                return callback(error);
            }

            if (result_addr && !config.access_point.force_reconfigure) {
                console.log("\nAccess point is enabled with ADDR: " + result_addr);
                return callback(null);
            } else if (config.access_point.force_reconfigure) {
                console.log("\nForce reconfigure enabled - reset AP");
            } else {
                console.log("\nAP is not enabled yet... enabling...");
            }

            var context = config.access_point;
            context["enable_ap"] = true;
            context["wifi_driver_type"] = config.wifi_driver_type;

            // Here we need to actually follow the steps to enable the ap
            async.series([

                // Enable the access point ip and netmask + static
                // DHCP for the wlan0 interface
                function update_interfaces(next_step) {
                    write_template_to_file(
                        "./assets/etc/network/interfaces.ap.template",
                        "/etc/network/interfaces",
                        context, next_step);
                },

                // Enable DHCP conf, set authoritative mode and subnet
                function update_dhcpd(next_step) {
                    var context = config.access_point;
                    // We must enable this to turn on the access point
                    write_template_to_file(
                        "./assets/etc/dhcp/dhcpd.conf.template",
                        "/etc/dhcp/dhcpd.conf",
                        context, next_step);
                },

                // Enable the interface in the dhcp server
                function update_dhcp_interface(next_step) {
                    write_template_to_file(
                        "./assets/etc/default/isc-dhcp-server.template",
                        "/etc/default/isc-dhcp-server",
                        context, next_step);
                },

                // Enable hostapd.conf file
                function update_hostapd_conf(next_step) {
                    write_template_to_file(
                        "./assets/etc/hostapd/hostapd.conf.template",
                        "/etc/hostapd/hostapd.conf",
                        context, next_step);
                },

                function update_hostapd_default(next_step) {
                    write_template_to_file(
                        "./assets/etc/default/hostapd.template",
                        "/etc/default/hostapd",
                        context, next_step);
                },
                function scan_networks(next_step){
		_reboot_wireless_network(context.wifi_interface,function(){
                  iwlist(function(error,result){
                    scanResult = result;
                    console.log(JSON.stringify(result));
			next_step();
                  });
		});
                },

                function reboot_network_interfaces(next_step) {
                    _reboot_wireless_network(context.wifi_interface, next_step);
                },

                function restart_dhcp_service(next_step) {
                    exec("service isc-dhcp-server restart", function(error, stdout, stderr) {
                        //console.log(stdout);
                        if (!error) console.log("... dhcp server restarted!");
                        next_step();
                    });
                },

                function restart_hostapd_service(next_step) {
                    exec("service hostapd restart", function(error, stdout, stderr) {
                        //console.log(stdout);
                        if (!error) console.log("... hostapd restarted!");
                        next_step();
                    });
                },

                // TODO: Do we need to issue a reboot here?

            ], callback(scanResult)
        });
    },

    // Disables AP mode and reverts to wifi connection
    _enable_wifi_mode = function(connection_info, callback) {

        _is_wifi_enabled(function(error, result_ip) {
            if (error) return callback(error);

            if (result_ip) {
                console.log("\nWifi connection is enabled with IP: " + result_ip);
                return callback(null);
            }

            async.series([

                // Update /etc/network/interface with correct info...
                function update_interfaces(next_step) {
                    write_template_to_file(
                        "./assets/etc/network/interfaces.wifi.template",
                        "/etc/network/interfaces",
                        connection_info, next_step);
                },

                // Stop the DHCP server...
                function restart_dhcp_service(next_step) {
                    exec("service isc-dhcp-server stop", function(error, stdout, stderr) {
                        //console.log(stdout);
                        if (!error) console.log("... dhcp server stopped!");
                        next_step();
                    });
                },

                function reboot_network_interfaces(next_step) {
                    _reboot_wireless_network(config.wifi_interface, next_step);
                },

            ], callback);
        });

    };

    return {
        get_wifi_info:           _get_wifi_info,
        reboot_wireless_network: _reboot_wireless_network,

        is_wifi_enabled:         _is_wifi_enabled,
        is_wifi_enabled_sync:    _is_wifi_enabled_sync,

        is_ap_enabled:           _is_ap_enabled,
        is_ap_enabled_sync:      _is_ap_enabled_sync,

        enable_ap_mode:          _enable_ap_mode,
        enable_wifi_mode:        _enable_wifi_mode,
    };
}
<commit_msg>fixed callback error in enable_ap_mode<commit_after>var _       = require("underscore")._,
    async   = require("async"),
    fs      = require("fs"),
    exec    = require("child_process").exec,
    iwlist     = require("./iwlist"),
    config  = require("../config.json");
var scanResult;

// Better template format
_.templateSettings = {
    interpolate: /\{\{(.+?)\}\}/g,
    evaluate :   /\{\[([\s\S]+?)\]\}/g
};

// Helper function to write a given template to a file based on a given
// context
function write_template_to_file(template_path, file_name, context, callback) {
    async.waterfall([

        function read_template_file(next_step) {
            fs.readFile(template_path, {encoding: "utf8"}, next_step);
        },

        function update_file(file_txt, next_step) {
            var template = _.template(file_txt);
            fs.writeFile(file_name, template(context), next_step);
        }

    ], callback);
}

/*****************************************************************************\
    Return a set of functions which we can use to manage and check our wifi
    connection information
\*****************************************************************************/
module.exports = function() {
    // Detect which wifi driver we should use, the rtl871xdrv or the nl80211
    exec("iw list", function(error, stdout, stderr) {
        if (stderr.match(/^nl80211 not found/)) {
            config.wifi_driver_type = "rtl871xdrv";
        }
        // console.log("config.wifi_driver_type = " + config.wifi_driver_type);
    });

    // Hack: this just assumes that the outbound interface will be "wlan0"

    // Define some globals
    var ifconfig_fields = {
        "hw_addr":         /HWaddr\s([^\s]+)/,
        "inet_addr":       /inet addr:([^\s]+)/,
    },  iwconfig_fields = {
        "ap_addr":         /Access Point:\s([^\s]+)/,
        "ap_ssid":         /ESSID:\"([^\"]+)\"/,
        "unassociated":    /(unassociated)\s+Nick/,
    },  last_wifi_info = null;

    // TODO: rpi-config-ap hardcoded, should derive from a constant

    // Get generic info on an interface
    var _get_wifi_info = function(callback) {
        var output = {
            hw_addr:      "<unknown>",
            inet_addr:    "<unknown>",
            unassociated: "<unknown>",
        };

        // Inner function which runs a given command and sets a bunch
        // of fields
        function run_command_and_set_fields(cmd, fields, callback) {
            exec(cmd, function(error, stdout, stderr) {
                if (error) return callback(error);
                for (var key in fields) {
                    re = stdout.match(fields[key]);
                    if (re && re.length > 1) {
                        output[key] = re[1];
                    }
                }
                callback(null);
            });
        }

        // Run a bunch of commands and aggregate info
        async.series([
            function run_ifconfig(next_step) {
                run_command_and_set_fields("ifconfig wlan0", ifconfig_fields, next_step);
            },
            function run_iwconfig(next_step) {
                run_command_and_set_fields("iwconfig wlan0", iwconfig_fields, next_step);
            },
        ], function(error) {
            last_wifi_info = output;
            return callback(error, output);
        });
    },

    _reboot_wireless_network = function(wlan_iface, callback) {
        async.series([
            function down(next_step) {
                exec("sudo ifdown " + wlan_iface, function(error, stdout, stderr) {
                    if (!error) console.log("ifdown " + wlan_iface + " successful...");
                    next_step();
                });
            },
            function up(next_step) {
                exec("sudo ifup " + wlan_iface, function(error, stdout, stderr) {
                    if (!error) console.log("ifup " + wlan_iface + " successful...");
                    next_step();
                });
            },
        ], callback);
    },

    // Wifi related functions
    _is_wifi_enabled_sync = function(info) {
        // If we are not an AP, and we have a valid
        // inet_addr - wifi is enabled!
        if (null        == _is_ap_enabled_sync(info) &&
            "<unknown>" != info["inet_addr"]         &&
            "<unknown>" == info["unassociated"] ) {
            return info["inet_addr"];
        }
        return null;
    },

    _is_wifi_enabled = function(callback) {
        _get_wifi_info(function(error, info) {
            if (error) return callback(error, null);
            return callback(null, _is_wifi_enabled_sync(info));
        });
    },

    // Access Point related functions
    _is_ap_enabled_sync = function(info) {
        // If the hw_addr matches the ap_addr
        // and the ap_ssid matches "rpi-config-ap"
        // then we are in AP mode
        var is_ap  =
            info["hw_addr"].toLowerCase() == info["ap_addr"].toLowerCase() &&
            info["ap_ssid"] == config.access_point.ssid;
        return (is_ap) ? info["hw_addr"].toLowerCase() : null;
    },

    _is_ap_enabled = function(callback) {
        _get_wifi_info(function(error, info) {
            if (error) return callback(error, null);
            return callback(null, _is_ap_enabled_sync(info));
        });
    },

    // Enables the accesspoint w/ bcast_ssid. This assumes that both
    // isc-dhcp-server and hostapd are installed using:
    // $sudo npm run-script provision
    _enable_ap_mode = function(bcast_ssid, callback) {
        _is_ap_enabled(function(error, result_addr) {
            if (error) {
                console.log("ERROR: " + error);
                return callback(error);
            }

            if (result_addr && !config.access_point.force_reconfigure) {
                console.log("\nAccess point is enabled with ADDR: " + result_addr);
                return callback(null);
            } else if (config.access_point.force_reconfigure) {
                console.log("\nForce reconfigure enabled - reset AP");
            } else {
                console.log("\nAP is not enabled yet... enabling...");
            }

            var context = config.access_point;
            context["enable_ap"] = true;
            context["wifi_driver_type"] = config.wifi_driver_type;

            // Here we need to actually follow the steps to enable the ap
            async.series([

                // Enable the access point ip and netmask + static
                // DHCP for the wlan0 interface
                function update_interfaces(next_step) {
                    write_template_to_file(
                        "./assets/etc/network/interfaces.ap.template",
                        "/etc/network/interfaces",
                        context, next_step);
                },

                // Enable DHCP conf, set authoritative mode and subnet
                function update_dhcpd(next_step) {
                    var context = config.access_point;
                    // We must enable this to turn on the access point
                    write_template_to_file(
                        "./assets/etc/dhcp/dhcpd.conf.template",
                        "/etc/dhcp/dhcpd.conf",
                        context, next_step);
                },

                // Enable the interface in the dhcp server
                function update_dhcp_interface(next_step) {
                    write_template_to_file(
                        "./assets/etc/default/isc-dhcp-server.template",
                        "/etc/default/isc-dhcp-server",
                        context, next_step);
                },

                // Enable hostapd.conf file
                function update_hostapd_conf(next_step) {
                    write_template_to_file(
                        "./assets/etc/hostapd/hostapd.conf.template",
                        "/etc/hostapd/hostapd.conf",
                        context, next_step);
                },

                function update_hostapd_default(next_step) {
                    write_template_to_file(
                        "./assets/etc/default/hostapd.template",
                        "/etc/default/hostapd",
                        context, next_step);
                },
                function scan_networks(next_step){
		_reboot_wireless_network(context.wifi_interface,function(){
                  iwlist(function(error,result){
                    scanResult = result;
                    console.log(JSON.stringify(result));
			next_step();
                  });
		});
                },

                function reboot_network_interfaces(next_step) {
                    _reboot_wireless_network(context.wifi_interface, next_step);
                },

                function restart_dhcp_service(next_step) {
                    exec("service isc-dhcp-server restart", function(error, stdout, stderr) {
                        //console.log(stdout);
                        if (!error) console.log("... dhcp server restarted!");
                        next_step();
                    });
                },

                function restart_hostapd_service(next_step) {
                    exec("service hostapd restart", function(error, stdout, stderr) {
                        //console.log(stdout);
                        if (!error) console.log("... hostapd restarted!");
                        next_step();
                    });
                },

                // TODO: Do we need to issue a reboot here?

            ], callback(null,scanResult))
        });
    },

    // Disables AP mode and reverts to wifi connection
    _enable_wifi_mode = function(connection_info, callback) {

        _is_wifi_enabled(function(error, result_ip) {
            if (error) return callback(error);

            if (result_ip) {
                console.log("\nWifi connection is enabled with IP: " + result_ip);
                return callback(null);
            }

            async.series([

                // Update /etc/network/interface with correct info...
                function update_interfaces(next_step) {
                    write_template_to_file(
                        "./assets/etc/network/interfaces.wifi.template",
                        "/etc/network/interfaces",
                        connection_info, next_step);
                },

                // Stop the DHCP server...
                function restart_dhcp_service(next_step) {
                    exec("service isc-dhcp-server stop", function(error, stdout, stderr) {
                        //console.log(stdout);
                        if (!error) console.log("... dhcp server stopped!");
                        next_step();
                    });
                },

                function reboot_network_interfaces(next_step) {
                    _reboot_wireless_network(config.wifi_interface, next_step);
                },

            ], callback);
        });

    };

    return {
        get_wifi_info:           _get_wifi_info,
        reboot_wireless_network: _reboot_wireless_network,

        is_wifi_enabled:         _is_wifi_enabled,
        is_wifi_enabled_sync:    _is_wifi_enabled_sync,

        is_ap_enabled:           _is_ap_enabled,
        is_ap_enabled_sync:      _is_ap_enabled_sync,

        enable_ap_mode:          _enable_ap_mode,
        enable_wifi_mode:        _enable_wifi_mode,
    };
}
<|endoftext|>


<commit_before>'use strict';
exports.BattleItems = {
	// VXN
	"wondergummi": {
		id: "wondergummi",
		name: "Wonder Gummi",
		spritenum: 538,
		naturalGift: {
			basePower: 200,
			type: "???",
		},
		isNonStandard: true,
		onUpdate: function (pokemon) {
			if (pokemon.hp <= pokemon.maxhp / 4 || (pokemon.hp <= pokemon.maxhp / 2 && pokemon.hasAbility('gluttony'))) {
				pokemon.eatItem();
			}
		},
		onTryEatItem: function (item, pokemon) {
			if (!this.runEvent('TryHeal', pokemon)) return false;
		},
		onEat: function (pokemon) {
			let fakecheck = this.random(1000);
			if (fakecheck <= 850) {
				this.add('message', 'Its belly felt full!');
				this.heal(pokemon.maxhp / 2);
				this.add('message', 'Its IQ rose!');
				this.boost({
					spd: 2,
				});
				this.boost({
					def: 2,
				});
			} else {
				this.add('message', 'Wait... Its a WANDER Gummi!');
				this.heal(pokemon.maxhp / 100);
				this.add('message', 'It gained the blinker status!');
				this.boost({
					accuracy: -6,
				});
			}
		},
		desc: "Either heals the user's max HP by 1/2, boosts the user's SpD and Def by 2 stages, or heals 1% of their health, but drops accuracy by six stages, when at 1/4 max HP or less, 1/2 if the user's ability is Gluttony. Single use.",
	},
	// Gligars
	"gravitysuit": {
		id: "gravitysuit",
		name: "Gravity Suit",
		spritenum: 581,
		fling: {
			basePower: 30,
		},
		onStart: function (pokemon) {
			this.useMove('Gravity', pokemon);
			this.useMove('Trick Room', pokemon);
		},
		isNonStandard: true,
		desc: "Uses Gravity and Trick Room on Switch-in.",
	},
	//megas4ever
	"spiritorb": {
		id: "spiritorb",
		name: "Spirit Orb",
		spritenum: 459,
		fling: {
			basePower: 30,
		},
		onBasePowerPriority: 6,
		onBasePower: function (basePower, user, target, move) {
			if (move && (move.type === 'Psychic') {
				return this.chainModify([0x1333, 0x1000]);
			}
		},
		onModifySpAPriority: 1,
		onModifySpA: function (spa) {
			return this.chainModify(1.5);
		},
		onModifySpDPriority: 1,
		onModifySpD: function (spd) {
			return this.chainModify(1.5);
		},
		desc: "1.5x SpA & SpD + Psychic moves do 1.3x more.",
	},
};
<commit_msg>SSBFFA: Fix megas4ever's item<commit_after>'use strict';
exports.BattleItems = {
	// VXN
	"wondergummi": {
		id: "wondergummi",
		name: "Wonder Gummi",
		spritenum: 538,
		naturalGift: {
			basePower: 200,
			type: "???",
		},
		isNonStandard: true,
		onUpdate: function (pokemon) {
			if (pokemon.hp <= pokemon.maxhp / 4 || (pokemon.hp <= pokemon.maxhp / 2 && pokemon.hasAbility('gluttony'))) {
				pokemon.eatItem();
			}
		},
		onTryEatItem: function (item, pokemon) {
			if (!this.runEvent('TryHeal', pokemon)) return false;
		},
		onEat: function (pokemon) {
			let fakecheck = this.random(1000);
			if (fakecheck <= 850) {
				this.add('message', 'Its belly felt full!');
				this.heal(pokemon.maxhp / 2);
				this.add('message', 'Its IQ rose!');
				this.boost({
					spd: 2,
				});
				this.boost({
					def: 2,
				});
			} else {
				this.add('message', 'Wait... Its a WANDER Gummi!');
				this.heal(pokemon.maxhp / 100);
				this.add('message', 'It gained the blinker status!');
				this.boost({
					accuracy: -6,
				});
			}
		},
		desc: "Either heals the user's max HP by 1/2, boosts the user's SpD and Def by 2 stages, or heals 1% of their health, but drops accuracy by six stages, when at 1/4 max HP or less, 1/2 if the user's ability is Gluttony. Single use.",
	},
	// Gligars
	"gravitysuit": {
		id: "gravitysuit",
		name: "Gravity Suit",
		spritenum: 581,
		fling: {
			basePower: 30,
		},
		onStart: function (pokemon) {
			this.useMove('Gravity', pokemon);
			this.useMove('Trick Room', pokemon);
		},
		isNonStandard: true,
		desc: "Uses Gravity and Trick Room on Switch-in.",
	},
	//megas4ever
	"spiritorb": {
		id: "spiritorb",
		name: "Spirit Orb",
		spritenum: 459,
		fling: {
			basePower: 30,
		},
		onBasePowerPriority: 6,
		onBasePower: function (basePower, user, target, move) {
			if (move && (move.type === 'Psychic')) {
				return this.chainModify([0x1333, 0x1000]);
			}
		},
		onModifySpAPriority: 1,
		onModifySpA: function (spa) {
			return this.chainModify(1.5);
		},
		onModifySpDPriority: 1,
		onModifySpD: function (spd) {
			return this.chainModify(1.5);
		},
		desc: "1.5x SpA & SpD + Psychic moves do 1.3x more.",
	},
};
<|endoftext|>


<commit_before>var crypto=require('crypto');
var formidable=require('formidable');
var DB;
const lifeTime=2592000;//有效時間(秒)，超時後從資料庫刪除，預設30天
const checkLifeTime=86400000;//清理間隔時間(毫秒)，檢查超時間隔時間，預設1天
const cacheTime=3600000;//快取有效時間(毫秒)，超時後寫出記憶體，預設1小時
const dbLogTime=600000;//寫入資料庫間隔時間(毫秒)，預設10分鐘
const formLimit={
	'maxFields': 3,
	'maxFieldsSize': 512
};

var maps={};

function Map(){
	var nowTime=new Date().getTime();
	this.id=null;//地圖id，null=未確認
	this.points={};//標記點
	this.links=[];//在線列表
	this.lastLogTime=nowTime;//最後記錄時間
	this.lastUpdateTime=nowTime;//最後更動時間
	this.lastReadTime=nowTime;//最後讀取時間
	this.updatedPoints=[];//上次紀錄後更動了哪些點
	this.deletedPoints=[];//上次紀錄後刪除了哪些點
}
Map.prototype.sendAll=function(msg){
	msg=JSON.stringify(msg);
	this.links.forEach(function(link){
		link.send(msg);
	});
}
Map.prototype.join=function(link){
	this.lastReadTime=new Date().getTime();
	this.links.push(link);
	var points=this.points;
	var point;
	for(var hashName in points){
		point=points[hashName];
		link.send(JSON.stringify({
			'action': 'move',
			'name': point.name,
			'pos': point.pos,
			'module': point.module,
			'args': point.args
		}));
	}
	points=undefined;
	delete point,points;
	var _=this;
	link.on('message',function(data){
		if(data.type!='utf8') return;
		console.log(data.utf8Data);
		var msg=JSON.parse(data.utf8Data);
		switch(msg.action){
			case 'move':
				if(msg.name===undefined) return;
				if(msg.pos!==undefined && (Number.isNaN(msg.pos[0]) || Number.isNaN(msg.pos[1]))) return;
				if(msg.module!==undefined && 'string'!==typeof msg.module) return;
				if(!Array.isArray(msg.args)) return;
				_.movePoint(msg.name,msg.pos,msg.module,msg.args);
			break;
			case 'delete':
				_.deletePoint(msg.name);
			break;
		}
	});
	link.on('close',function(){
		_.lastReadTime=new Date().getTime();
		_.links.splice(_.links.indexOf(link),1);
		_=link=undefined;
		delete _,link;
	});
	link.send('{"action":"viewAll"}');
}
Map.prototype.movePoint=function(name,pos,module,args){
	if(name===undefined) return;
	var hashName=md5(name);
	var point
	if(!(point=this.points[hashName])){
		if(pos===undefined) return;
		point=this.points[hashName]={
			'name': name,
			'pos': pos,
			'module': '',
			'args': []
		};
	}else if(pos!=undefined) point.pos=pos;
	if(module!=undefined) point.module=module;
	if(args!=undefined) point.args=args;
	this.lastUpdateTime=new Date().getTime();
	if(this.updatedPoints.indexOf(hashName)<0)
		this.updatedPoints.push(hashName);
	this.sendAll({
		'action': 'move',
		'name': name,
		'pos': pos,
		'module': module,
		'args': args
	});
}
Map.prototype.deletePoint=function(name){
	var hashName=md5(name);
	if(!this.points[hashName]) return;
	var pointId=this.points[hashName].id;
	if(pointId){
		if(this.deletedPoints.indexOf(pointId)<0)
			this.deletedPoints.push(pointId);
		var checkUpdated;
		if(checkUpdated=this.updatedPoints.indexOf(name)>=0)
			this.updatedPoints.splice(checkUpdated,1);
	}
	this.points[hashName]=undefined;
	delete this.points[hashName];
	this.lastUpdateTime=new Date().getTime();
	this.sendAll({
		'action': 'delete',
		'name': name
	});
}

function md5(data){
	return crypto.createHash('md5').update(data).digest('hex');
}
function getMap(name){
	var hashName=md5(name);
	var map;
	if(map=maps[hashName])
		return map;
	map=maps[hashName]=new Map();
	DB.query(
		'SELECT `points`.* \
		FROM `maps`,`points` \
		WHERE `maps`.`name`=? AND `maps`.`id`=`points`.`mapId`',
		[hashName],
		function(err,points){
			if(!points.length){
				map.id=false;
				return;
			}
			map.id=points[0].mapId;
			points.forEach(function(point){
				var hashName=md5(point.name);
				if(this.points[hashName]){
					this.points[hashName].id=point.id;
					return;
				}
				point=this.points[hashName]={
					'id': point.id,
					'name': point.name,
					'pos': [point.posX,point.posY],
					'module': point.module,
					'args': JSON.parse(point.args)
				};
				this.sendAll({
					'action': 'move',
					'name': point.name,
					'pos': point.pos,
					'module': point.module,
					'args': point.args
				});
			},map);
			map.sendAll({'action': 'viewAll'});
		}
	);
	return map;
}
var writeDB=(function(){
	var createMap=function(hashName,callback){
		var map=maps[hashName];
		DB.query(
			'INSERT INTO `maps` (`name`,`lastReadTime`) VALUE (?,?)',
			[hashName,Math.floor(map.lastReadTime/1000)],
			function(err,result){
				if(err){
					console.log('[mapPoint] 無法建立地圖 hashName=%s',hashName);
					return;
				}
				map.id=result.insertId;
				writeMap(hashName,callback);
			}
		);
	};
	var writeMap=function(hashName,callback){
		var waitQueryCount=0;
		var map=maps[hashName];
		var mapId=map.id;
		map.lastLogTime=new Date().getTime();
		waitQueryCount++;
		DB.query(
			'UPDATE `maps` SET `lastReadTime`=? WHERE `id`=?',
			[Math.floor(map.lastReadTime/1000),mapId],
			function(err){
				if(err){
					console.log('[mapPoint] 地圖最後讀取時間更新失敗 id=%d',mapId);
				}
				--waitQueryCount || callback();
			}
		);
		if(map.updatedPoints.length){
			map.updatedPoints.forEach(function(key,index,o){
				var point=this[key];
				waitQueryCount++;
				if(point.id)
					DB.query(
						'UPDATE `points` SET `posX`=?, `posY`=?, `module`=?,`args`=? WHERE `id`=?',
						[point.pos[0],point.pos[1],point.module,JSON.stringify(point.args),point.id],
						function(err){
							if(err)
								console.log('[mapPoint] 更新標記點失敗 id=%d',point.id);
							point=undefined;
							delete point;
							--waitQueryCount || callback();
						}
					);
				else
					DB.query(
						'INSERT INTO `points` (`mapId`,`name`,`posX`,`posY`,`module`,`args`) VALUE (?,?,?,?,?,?)',
						[mapId,point.name,point.pos[0],point.pos[1],point.module,JSON.stringify(point.args)],
						function(err,result){
							if(err)
								console.log('[mapPoint] 新增標記點失敗 map=%s,name=%s',hashName,point.name);
							else
								point.id=result.insertId;
							point=undefined;
							delete point;
							--waitQueryCount || callback();
						}
					);
			},map.points);
			map.updatedPoints=[];
		}
		if(map.deletedPoints.length){
			var list=map.deletedPoints;
			waitQueryCount++;
			DB.query('DELETE FROM `point` WHERE `id` IN (?)',[list],function(err){
				if(err){
					console.log('[mapPoint] 刪除標記點失敗 id=%s',list.join(','));
				}
				list=undefined;
				delete list;
				--waitQueryCount || callback();
			});
			map.deletedPoints=[];
		}
	};
	return function(callback){
		var jobCount=0;
		var cb=function(){
			--jobCount || callback===undefined || callback();
		};
		var outTime=new Date().getTime()-cacheTime;
		var hashName,map;
		for(hashName in maps){
			map=maps[hashName];
			if(map.updatedPoints.length || map.deletedPoints.length){
				jobCount++;
				if(map.id)
					writeMap(hashName,cb);
				else
					createMap(hashName,cb);
			}
			if(map.links.length && map.lastReadTime<outTime){
				maps[hashName]=undefined;
				delete maps[hashName];
			}
		}
		map=undefined;
		delete map,hashName;
	};
})();

//定時分散寫入
setInterval(writeDB,dbLogTime);
//定時清理資料庫
setInterval(function(){
	var expTime=Math.floor(new Date().getTime()/1000-lifeTime);
	DB.query('SELECT `id`,`name` FROM `maps` WHERE `lastReadTime`<?',[expTime],function(err,deleteList){
		deleteList=deleteList.reduce(function(r,v){
			if(maps[v.name]) return r;
			r.push(v.id);
			return r;
		},[]);
		DB.query('DELETE FROM `maps` WHERE `id` IN (?)',[deleteList],function(err){
			if(err){
				console.log('[mapPoint] 清理地圖資料失敗，目標清單 id=%s，錯誤訊息：%s',deleteList.join(','),err);
			}
		});
		DB.query('DELETE FROM `points` WHERE `mapId` IN (?)',[deleteList],function(err){
			if(err){
				console.log('[mapPoint] 清理標記點資料失敗，目標清單 id=%s，錯誤訊息：%s',deleteList.join(','),err);
			}
		});
	});
},checkLifeTime);

module.exports={
	'load': function(control){
		DB=control.connectDB();
		var loaded=(function(control){
			var step=2;
			return function(){
				if(--step) return;
				delete step;
				loaded=undefined;
				delete loaded;
				control.onLoad('mapPoint');
			}
		})(control);
		DB.query(
			'CREATE TABLE IF NOT EXISTS `maps` (\
				`id` int(10) unsigned NOT NULL AUTO_INCREMENT,\
				`name` char(32) NOT NULL,\
				`lastReadTime` int(10) unsigned NOT NULL,\
				PRIMARY KEY (`id`),\
				KEY `mapId` (`name`)\
			) ENGINE=MyISAM  DEFAULT CHARSET=latin1 COMMENT=\'地圖\' AUTO_INCREMENT=1;',
			function(err){
				if(!err){
					loaded();
					return;
				}
				console.error('[mapPoint] 表 maps 檢查或建立失敗');
				process.exit();
			}
		);
		DB.query(
			'CREATE TABLE IF NOT EXISTS `points` (\
				`id` int(10) unsigned NOT NULL AUTO_INCREMENT,\
				`mapId` int(10) unsigned NOT NULL,\
				`name` varchar(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,\
				`posX` double(10,7) NOT NULL,\
				`posY` double(10,7) NOT NULL,\
				`module` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,\
				`args` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,\
				PRIMARY KEY (`id`),\
				KEY `mapId` (`mapId`)\
			) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT=\'標記點\' AUTO_INCREMENT=1;',
			function(err){
				if(!err){
					loaded();
					return;
				}
				console.error('[mapPoint] 表 points 檢查或建立失敗');
				process.exit();
			}
		);
		control.mountHttp('post','/:name(\\w+)',function(req,res){
			var name=req.params.name;
			var map=getMap(name);
			var form=new formidable.IncomingForm(formLimit);
			form.on('error',function(e){
				req.connection.destroy();
			});
			form.parse(req, function(err,fields){
				switch(fields.action){
					case 'move':
						var pos=fields.pos.split(',');
						pos[0]=parseFloat(pos[0],10);
						pos[1]=parseFloat(pos[1],10);
						var args=JSON.parse(fields.args);
						if(Number.isNaN(pos[0]) || Number.isNaN(pos[1]) || !Array.isArray(args)){
							res.writeHead(400);
							res.end();
							return;
						}
						map.movePoint(fields.name,pos,fields.module,args);
					break;
					case 'delete':
						map.deletePoint(fields.name);
					break;
					default:
						res.writeHead(404);
						res.end();
						return;
				}
				res.writeHead(200);
				res.end();
			});
		});
		control.mountWebSocket('mapPoint',/^\/\w+$/,function(url,link){
			var name=url.pathname.substring(1,url.pathname.length);
			getMap(name).join(link);
		});
	},
	'unload': function(control){
		for(var hashName in maps){
			maps[hashName].links.forEach(function(link){
				link.close();
			});
		}
		writeDB(function(){
			control.onUnload('mapPoint');
		});
	}
};<commit_msg>[mapPoint] fix: 沒有地圖的情況下無法結束程式<commit_after>var crypto=require('crypto');
var formidable=require('formidable');
var DB;
const lifeTime=2592000;//有效時間(秒)，超時後從資料庫刪除，預設30天
const checkLifeTime=86400000;//清理間隔時間(毫秒)，檢查超時間隔時間，預設1天
const cacheTime=3600000;//快取有效時間(毫秒)，超時後寫出記憶體，預設1小時
const dbLogTime=600000;//寫入資料庫間隔時間(毫秒)，預設10分鐘
const formLimit={
	'maxFields': 3,
	'maxFieldsSize': 512
};

var maps={};

function Map(){
	var nowTime=new Date().getTime();
	this.id=null;//地圖id，null=未確認
	this.points={};//標記點
	this.links=[];//在線列表
	this.lastLogTime=nowTime;//最後記錄時間
	this.lastUpdateTime=nowTime;//最後更動時間
	this.lastReadTime=nowTime;//最後讀取時間
	this.updatedPoints=[];//上次紀錄後更動了哪些點
	this.deletedPoints=[];//上次紀錄後刪除了哪些點
}
Map.prototype.sendAll=function(msg){
	msg=JSON.stringify(msg);
	this.links.forEach(function(link){
		link.send(msg);
	});
}
Map.prototype.join=function(link){
	this.lastReadTime=new Date().getTime();
	this.links.push(link);
	var points=this.points;
	var point;
	for(var hashName in points){
		point=points[hashName];
		link.send(JSON.stringify({
			'action': 'move',
			'name': point.name,
			'pos': point.pos,
			'module': point.module,
			'args': point.args
		}));
	}
	points=undefined;
	delete point,points;
	var _=this;
	link.on('message',function(data){
		if(data.type!='utf8') return;
		console.log(data.utf8Data);
		var msg=JSON.parse(data.utf8Data);
		switch(msg.action){
			case 'move':
				if(msg.name===undefined) return;
				if(msg.pos!==undefined && (Number.isNaN(msg.pos[0]) || Number.isNaN(msg.pos[1]))) return;
				if(msg.module!==undefined && 'string'!==typeof msg.module) return;
				if(!Array.isArray(msg.args)) return;
				_.movePoint(msg.name,msg.pos,msg.module,msg.args);
			break;
			case 'delete':
				_.deletePoint(msg.name);
			break;
		}
	});
	link.on('close',function(){
		_.lastReadTime=new Date().getTime();
		_.links.splice(_.links.indexOf(link),1);
		_=link=undefined;
		delete _,link;
	});
	link.send('{"action":"viewAll"}');
}
Map.prototype.movePoint=function(name,pos,module,args){
	if(name===undefined) return;
	var hashName=md5(name);
	var point
	if(!(point=this.points[hashName])){
		if(pos===undefined) return;
		point=this.points[hashName]={
			'name': name,
			'pos': pos,
			'module': '',
			'args': []
		};
	}else if(pos!=undefined) point.pos=pos;
	if(module!=undefined) point.module=module;
	if(args!=undefined) point.args=args;
	this.lastUpdateTime=new Date().getTime();
	if(this.updatedPoints.indexOf(hashName)<0)
		this.updatedPoints.push(hashName);
	this.sendAll({
		'action': 'move',
		'name': name,
		'pos': pos,
		'module': module,
		'args': args
	});
}
Map.prototype.deletePoint=function(name){
	var hashName=md5(name);
	if(!this.points[hashName]) return;
	var pointId=this.points[hashName].id;
	if(pointId){
		if(this.deletedPoints.indexOf(pointId)<0)
			this.deletedPoints.push(pointId);
		var checkUpdated;
		if(checkUpdated=this.updatedPoints.indexOf(name)>=0)
			this.updatedPoints.splice(checkUpdated,1);
	}
	this.points[hashName]=undefined;
	delete this.points[hashName];
	this.lastUpdateTime=new Date().getTime();
	this.sendAll({
		'action': 'delete',
		'name': name
	});
}

function md5(data){
	return crypto.createHash('md5').update(data).digest('hex');
}
function getMap(name){
	var hashName=md5(name);
	var map;
	if(map=maps[hashName])
		return map;
	map=maps[hashName]=new Map();
	DB.query(
		'SELECT `points`.* \
		FROM `maps`,`points` \
		WHERE `maps`.`name`=? AND `maps`.`id`=`points`.`mapId`',
		[hashName],
		function(err,points){
			if(!points.length){
				map.id=false;
				return;
			}
			map.id=points[0].mapId;
			points.forEach(function(point){
				var hashName=md5(point.name);
				if(this.points[hashName]){
					this.points[hashName].id=point.id;
					return;
				}
				point=this.points[hashName]={
					'id': point.id,
					'name': point.name,
					'pos': [point.posX,point.posY],
					'module': point.module,
					'args': JSON.parse(point.args)
				};
				this.sendAll({
					'action': 'move',
					'name': point.name,
					'pos': point.pos,
					'module': point.module,
					'args': point.args
				});
			},map);
			map.sendAll({'action': 'viewAll'});
		}
	);
	return map;
}
var writeDB=(function(){
	var createMap=function(hashName,callback){
		var map=maps[hashName];
		DB.query(
			'INSERT INTO `maps` (`name`,`lastReadTime`) VALUE (?,?)',
			[hashName,Math.floor(map.lastReadTime/1000)],
			function(err,result){
				if(err){
					console.log('[mapPoint] 無法建立地圖 hashName=%s',hashName);
					return;
				}
				map.id=result.insertId;
				writeMap(hashName,callback);
			}
		);
	};
	var writeMap=function(hashName,callback){
		var waitQueryCount=0;
		var map=maps[hashName];
		var mapId=map.id;
		map.lastLogTime=new Date().getTime();
		waitQueryCount++;
		DB.query(
			'UPDATE `maps` SET `lastReadTime`=? WHERE `id`=?',
			[Math.floor(map.lastReadTime/1000),mapId],
			function(err){
				if(err){
					console.log('[mapPoint] 地圖最後讀取時間更新失敗 id=%d',mapId);
				}
				--waitQueryCount || callback();
			}
		);
		if(map.updatedPoints.length){
			map.updatedPoints.forEach(function(key,index,o){
				var point=this[key];
				waitQueryCount++;
				if(point.id)
					DB.query(
						'UPDATE `points` SET `posX`=?, `posY`=?, `module`=?,`args`=? WHERE `id`=?',
						[point.pos[0],point.pos[1],point.module,JSON.stringify(point.args),point.id],
						function(err){
							if(err)
								console.log('[mapPoint] 更新標記點失敗 id=%d',point.id);
							point=undefined;
							delete point;
							--waitQueryCount || callback();
						}
					);
				else
					DB.query(
						'INSERT INTO `points` (`mapId`,`name`,`posX`,`posY`,`module`,`args`) VALUE (?,?,?,?,?,?)',
						[mapId,point.name,point.pos[0],point.pos[1],point.module,JSON.stringify(point.args)],
						function(err,result){
							if(err)
								console.log('[mapPoint] 新增標記點失敗 map=%s,name=%s',hashName,point.name);
							else
								point.id=result.insertId;
							point=undefined;
							delete point;
							--waitQueryCount || callback();
						}
					);
			},map.points);
			map.updatedPoints=[];
		}
		if(map.deletedPoints.length){
			var list=map.deletedPoints;
			waitQueryCount++;
			DB.query('DELETE FROM `point` WHERE `id` IN (?)',[list],function(err){
				if(err){
					console.log('[mapPoint] 刪除標記點失敗 id=%s',list.join(','));
				}
				list=undefined;
				delete list;
				--waitQueryCount || callback();
			});
			map.deletedPoints=[];
		}
	};
	return function(callback){
		var jobCount=0;
		var cb=function(){
			--jobCount || callback===undefined || callback();
		};
		var outTime=new Date().getTime()-cacheTime;
		var hashName,map;
		for(hashName in maps){
			map=maps[hashName];
			if(map.updatedPoints.length || map.deletedPoints.length){
				jobCount++;
				if(map.id)
					writeMap(hashName,cb);
				else
					createMap(hashName,cb);
			}
			if(map.links.length && map.lastReadTime<outTime){
				maps[hashName]=undefined;
				delete maps[hashName];
			}
		}
		map=undefined;
		delete map,hashName;
		jobCount || callback===undefined || callback();
	};
})();

//定時分散寫入
setInterval(writeDB,dbLogTime);
//定時清理資料庫
setInterval(function(){
	var expTime=Math.floor(new Date().getTime()/1000-lifeTime);
	DB.query('SELECT `id`,`name` FROM `maps` WHERE `lastReadTime`<?',[expTime],function(err,deleteList){
		deleteList=deleteList.reduce(function(r,v){
			if(maps[v.name]) return r;
			r.push(v.id);
			return r;
		},[]);
		DB.query('DELETE FROM `maps` WHERE `id` IN (?)',[deleteList],function(err){
			if(err){
				console.log('[mapPoint] 清理地圖資料失敗，目標清單 id=%s，錯誤訊息：%s',deleteList.join(','),err);
			}
		});
		DB.query('DELETE FROM `points` WHERE `mapId` IN (?)',[deleteList],function(err){
			if(err){
				console.log('[mapPoint] 清理標記點資料失敗，目標清單 id=%s，錯誤訊息：%s',deleteList.join(','),err);
			}
		});
	});
},checkLifeTime);

module.exports={
	'load': function(control){
		DB=control.connectDB();
		var loaded=(function(control){
			var step=2;
			return function(){
				if(--step) return;
				delete step;
				loaded=undefined;
				delete loaded;
				control.onLoad('mapPoint');
			}
		})(control);
		DB.query(
			'CREATE TABLE IF NOT EXISTS `maps` (\
				`id` int(10) unsigned NOT NULL AUTO_INCREMENT,\
				`name` char(32) NOT NULL,\
				`lastReadTime` int(10) unsigned NOT NULL,\
				PRIMARY KEY (`id`),\
				KEY `mapId` (`name`)\
			) ENGINE=MyISAM  DEFAULT CHARSET=latin1 COMMENT=\'地圖\' AUTO_INCREMENT=1;',
			function(err){
				if(!err){
					loaded();
					return;
				}
				console.error('[mapPoint] 表 maps 檢查或建立失敗');
				process.exit();
			}
		);
		DB.query(
			'CREATE TABLE IF NOT EXISTS `points` (\
				`id` int(10) unsigned NOT NULL AUTO_INCREMENT,\
				`mapId` int(10) unsigned NOT NULL,\
				`name` varchar(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,\
				`posX` double(10,7) NOT NULL,\
				`posY` double(10,7) NOT NULL,\
				`module` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,\
				`args` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,\
				PRIMARY KEY (`id`),\
				KEY `mapId` (`mapId`)\
			) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT=\'標記點\' AUTO_INCREMENT=1;',
			function(err){
				if(!err){
					loaded();
					return;
				}
				console.error('[mapPoint] 表 points 檢查或建立失敗');
				process.exit();
			}
		);
		control.mountHttp('post','/:name(\\w+)',function(req,res){
			var name=req.params.name;
			var map=getMap(name);
			var form=new formidable.IncomingForm(formLimit);
			form.on('error',function(e){
				req.connection.destroy();
			});
			form.parse(req, function(err,fields){
				switch(fields.action){
					case 'move':
						var pos=fields.pos.split(',');
						pos[0]=parseFloat(pos[0],10);
						pos[1]=parseFloat(pos[1],10);
						var args=JSON.parse(fields.args);
						if(Number.isNaN(pos[0]) || Number.isNaN(pos[1]) || !Array.isArray(args)){
							res.writeHead(400);
							res.end();
							return;
						}
						map.movePoint(fields.name,pos,fields.module,args);
					break;
					case 'delete':
						map.deletePoint(fields.name);
					break;
					default:
						res.writeHead(404);
						res.end();
						return;
				}
				res.writeHead(200);
				res.end();
			});
		});
		control.mountWebSocket('mapPoint',/^\/\w+$/,function(url,link){
			var name=url.pathname.substring(1,url.pathname.length);
			getMap(name).join(link);
		});
	},
	'unload': function(control){
		for(var hashName in maps){
			maps[hashName].links.forEach(function(link){
				link.close();
			});
		}
		writeDB(function(){
			control.onUnload('mapPoint');
		});
	}
};<|endoftext|>