<commit_before>exports.BattleMovedex = {
        "vinewhip": {
                num: 22,
                accuracy: 100,
                basePower: 35,
                category: "Physical",
                desc: "Deals damage to one adjacent target. Makes contact.",
                shortDesc: "No additional effect.",
                id: "vinewhip",
                name: "Vine Whip",
                pp: 25,
                priority: 0,
                isContact: true,
                secondary: false,
                target: "normal",
                type: "Grass"
        },
      	"lunardance": {
		num: 461,
		accuracy: true,
		basePower: 0,
		category: "Status",
		desc: "The user faints and the Pokemon brought out to replace it has its HP and PP fully restored along with having any major status condition cured. Fails if the user is the last unfainted Pokemon in its party.",
		shortDesc: "User faints. Replacement is fully healed, with PP.",
		id: "lunardance",
		isViable: true,
		name: "Lunar Dance",
		pp: 20,
		priority: 0,
		isSnatchable: true,
		boosts: {
			spa: 1,
			spe: 1
		},
		target: "self",
		type: "Psychic"
	},
	"moonblast": {
		num: 1000,
		accuracy: 90,
		basePower: 130,
		category: "Special",
		desc: "Surronds the user with moonlight energy and releases all of that energy in one blast using up all the energy making the attacks Special Attack go down.",
		shortDesc: "Deals damage and lowers the attacker's SpA.",
		id: "moonblast",
		isViable: true,
		name: "Moonblast",
		pp: 5,
		priority: 0,
		self: {
			boosts: {
				spa: -2

			}
		},
		secondary: false,
		target: "normal",
		type: "Fairy"
	},
	"fairywind": {
		num: 1001,
	        accuracy: 100,
		basePower: 85,
		category: "Special",
		desc: "Deals damage to one adjacent target by summoning a mystical wind that has a chance to encloak the user with mystical energy that boosts speed.",
		shortDesc: "Deals damage and has 10% chance to raise speed.",
		id: "fairywind",
		isViable: true,
		name: "Fairy Wind",
		pp: 15,
		priority: 0,
		secondary: {
			chance: 10,
			self: {
				boosts: {
					spd: 1
				}
			}
		},
		target: "normal",
		type: "Fairy"
	},
		"mysticcrash": {
		num: 1002,
	        accuracy: 100,
		basePower: 120,
		category: "Physical",
		desc: "Deals damage to one adjacent target by crashing into the target with a cloak of mystical energy.",
		shortDesc: "Deals damage and has recoil Basically Double Edge for faries.",
		id: "mysticcrash",
		isViable: true,
		name: "Mystic Crash",
		pp: 15,
		priority: 0,
		isContact: true,
		recoil: [22,100],
		secondary: false,
		target: "normal",
		type: "Fairy"
	},
        "partingshot": {
                num: 1032,
                accuracy: 100,
                basePower: 75,
                category: "Status",
                desc: "Lowers the opponent's Special Attack and Attack by 1, then switches the user out of battle.",
                shortDesc: "User switches out after lowering the target's SpA and Atk.",
                id: "partingshot",
                name: "Parting Shot",
                pp: 25,
                priority: 0,
                boosts: {
                        atk: -1,
                        spa: -1
                },
                selfSwitch: true,
                secondary: false,
                target: "normal",
                type: "Fighting"
        },
        "paraboliccharge": {
                num: 1033,
                accuracy: 100,
                basePower: 80,
                category: "Special",
                desc: "Deals damage to all targets, including its ally. The user recovers half of the HP lost by the target, rounded up. If Big Root is held by the user, the HP recovered is 1.3x normal, rounded half down.",
                shortDesc: "User recovers 50% of the damage dealt.",
                id: "paraboliccharge",
                name: "Parabolic Charge",
                pp: 16,
                priority: 0,
                drain: [1,2],
                secondary: false,
                target: "normal",
                type: "Electric"
        },
        "boomburst": {
                num: 1034,
                accuracy: 100,
                basePower: 100,
                category: "Special",
                desc: "A sound attack that deals damage to all targets, including its ally.",
                shortDesc: "Deals damage to all targets.",
                id: "boomburst",
                name: "Boomburst",
                pp: 16,
                priority: 0,
                isSoundBased: true,
                secondary: false,
                target: "all",
                type: "Normal"
        },
        "moonblast": {
                num: 1035,
                accuracy: 100,
                basePower: 110,
                category: "Special",
                desc: "Deals damage to one adjacent target with a ???% chance to lower Special Attack",
                shortDesc: "30% chance to lower SpA.",
                id: "moonblast",
                name: "Moonblast",
                pp: 25,
                priority: 0,
                secondary: {
                        chance: 30,
                        boosts: {
                                spa: -1
                        }
                },
                target: "normal",
                type: "Fairy"
        },
        "nobleroar": {
                num: 1036,
                accuracy: 100,
                basePower: 85,
                category: "Status",
                desc: "Lowers the foe's Attack and Special Attack by one stage.",
                shortDesc: "Lowers SpA and Atk by 1.",
                id: "nobleroar",
                name: "Noble Roar",
                pp: 16,
                priority: 0,
                boosts: {
                        atk: -1,
                        spa: -1
                },
                secondary: false,
                target: "normal",
                type: "Fire"
        },
        "fairywind": {
                num: 1037,
                accuracy: 100,
                basePower: 85,
                category: "Special",
                desc: "Deals massive damage to all adjacent foes.",
                shortDesc: "Damages all adjacent foes.",
                id: "fairywind",
                name: "Fairy Wind",
                pp: 25,
                priority: 0,
                secondary: false,
                target: "allAdjacentFoes",
                type: "Fairy"
        },
        "topsyturvy": {
                num: 1038,
                accuracy: 100,
                basePower: 0,
                category: "Status",
                desc: "Reverses all stat modifications that the foe has.",
                shortDesc: "Reverses the foe's stat changes.",
                id: "topsyturvy",
                name: "Topsy-Turvy",
                pp: 16,
                priority: 0,
                onHit: function(target) {
                         for (var i in target.boosts) {
                               target.boosts[i] = target.boosts[i] * (-1);
                        }
                        this.add('-reverseboost', target, '[from] move: Topsy-Turvy');
			}
                },
                secondary: false,
                target: "normal",
                type: "Fairy"
        },
        "drainingkiss": {
                num: 1039,
                accuracy: 100,
                basePower: 80,
                category: "Special",
                desc: "Deals damage to one adjacent target. The user recovers half of the HP lost by the target, rounded up. If Big Root is held by the user, the HP recovered is 1.3x normal, rounded half down.",
                shortDesc: "User recovers 50% of the damage dealt.",
                id: "drainingkiss",
                name: "Draining Kiss",
                pp: 16,
                priority: 0,
                drain: [1,2],
                secondary: false,
                target: "normal",
                type: "Fairy"
        },
        "geomancy": {
                num: 1040,
                accuracy: 100,
                basePower: 130,
                category: "Special",
                desc: "Deal massive damage to one adjacent target.",
                shortDesc: "No additional effect.",
                id: "geomancy",
                name: "Geomancy",
                pp: 16,
                priority: 0,
		self: {
			boosts: {
				spa: -1,
                                spd: -1
			}
		},
                secondary: false,
                target: "all",
                type: "Fairy"
        },
        "oblivionwing": {
                num: 1041,
                accuracy: 100,
                basePower: 130,
                category: "Special",
                desc: "Deal massive damage to one adjacent target.",
                shortDesc: "No additional effect.",
                id: "oblivionwing",
                name: "Oblivion Wing",
                pp: 16,
        	priority: 0,
		self: {
			boosts: {
				spa: -1,
                                spd: -1
			}
		},
                secondary: false,
                target: "all",
                type: "Dark"
	}
};
<commit_msg>Update moves.js<commit_after>exports.BattleMovedex = {
        "vinewhip": {
                num: 22,
                accuracy: 100,
                basePower: 35,
                category: "Physical",
                desc: "Deals damage to one adjacent target. Makes contact.",
                shortDesc: "No additional effect.",
                id: "vinewhip",
                name: "Vine Whip",
                pp: 25,
                priority: 0,
                isContact: true,
                secondary: false,
                target: "normal",
                type: "Grass"
        },
      	"lunardance": {
		num: 461,
		accuracy: true,
		basePower: 0,
		category: "Status",
		desc: "The user faints and the Pokemon brought out to replace it has its HP and PP fully restored along with having any major status condition cured. Fails if the user is the last unfainted Pokemon in its party.",
		shortDesc: "User faints. Replacement is fully healed, with PP.",
		id: "lunardance",
		isViable: true,
		name: "Lunar Dance",
		pp: 20,
		priority: 0,
		isSnatchable: true,
		boosts: {
			spa: 1,
			spe: 1
		},
		target: "self",
		type: "Psychic"
	},
	"moonblast": {
		num: 1000,
		accuracy: 90,
		basePower: 130,
		category: "Special",
		desc: "Surronds the user with moonlight energy and releases all of that energy in one blast using up all the energy making the attacks Special Attack go down.",
		shortDesc: "Deals damage and lowers the attacker's SpA.",
		id: "moonblast",
		isViable: true,
		name: "Moonblast",
		pp: 5,
		priority: 0,
		self: {
			boosts: {
				spa: -2

			}
		},
		secondary: false,
		target: "normal",
		type: "Fairy"
	},
	"fairywind": {
		num: 1001,
	        accuracy: 100,
		basePower: 85,
		category: "Special",
		desc: "Deals damage to one adjacent target by summoning a mystical wind that has a chance to encloak the user with mystical energy that boosts speed.",
		shortDesc: "Deals damage and has 10% chance to raise speed.",
		id: "fairywind",
		isViable: true,
		name: "Fairy Wind",
		pp: 15,
		priority: 0,
		secondary: {
			chance: 10,
			self: {
				boosts: {
					spd: 1
				}
			}
		},
		target: "normal",
		type: "Fairy"
	},
		"mysticcrash": {
		num: 1002,
	        accuracy: 100,
		basePower: 120,
		category: "Physical",
		desc: "Deals damage to one adjacent target by crashing into the target with a cloak of mystical energy.",
		shortDesc: "Deals damage and has recoil Basically Double Edge for faries.",
		id: "mysticcrash",
		isViable: true,
		name: "Mystic Crash",
		pp: 15,
		priority: 0,
		isContact: true,
		recoil: [22,100],
		secondary: false,
		target: "normal",
		type: "Fairy"
	},
        "partingshot": {
                num: 1032,
                accuracy: 100,
                basePower: 75,
                category: "Status",
                desc: "Lowers the opponent's Special Attack and Attack by 1, then switches the user out of battle.",
                shortDesc: "User switches out after lowering the target's SpA and Atk.",
                id: "partingshot",
                name: "Parting Shot",
                pp: 25,
                priority: 0,
                boosts: {
                        atk: -1,
                        spa: -1
                },
                selfSwitch: true,
                secondary: false,
                target: "normal",
                type: "Fighting"
        },
        "paraboliccharge": {
                num: 1033,
                accuracy: 100,
                basePower: 80,
                category: "Special",
                desc: "Deals damage to all targets, including its ally. The user recovers half of the HP lost by the target, rounded up. If Big Root is held by the user, the HP recovered is 1.3x normal, rounded half down.",
                shortDesc: "User recovers 50% of the damage dealt.",
                id: "paraboliccharge",
                name: "Parabolic Charge",
                pp: 16,
                priority: 0,
                drain: [1,2],
                secondary: false,
                target: "normal",
                type: "Electric"
        },
        "boomburst": {
                num: 1034,
                accuracy: 100,
                basePower: 100,
                category: "Special",
                desc: "A sound attack that deals damage to all targets, including its ally.",
                shortDesc: "Deals damage to all targets.",
                id: "boomburst",
                name: "Boomburst",
                pp: 16,
                priority: 0,
                isSoundBased: true,
                secondary: false,
                target: "all",
                type: "Normal"
        },
        "moonblast": {
                num: 1035,
                accuracy: 100,
                basePower: 110,
                category: "Special",
                desc: "Deals damage to one adjacent target with a ???% chance to lower Special Attack",
                shortDesc: "30% chance to lower SpA.",
                id: "moonblast",
                name: "Moonblast",
                pp: 25,
                priority: 0,
                secondary: {
                        chance: 30,
                        boosts: {
                                spa: -1
                        }
                },
                target: "normal",
                type: "Fairy"
        },
        "nobleroar": {
                num: 1036,
                accuracy: 100,
                basePower: 85,
                category: "Status",
                desc: "Lowers the foe's Attack and Special Attack by one stage.",
                shortDesc: "Lowers SpA and Atk by 1.",
                id: "nobleroar",
                name: "Noble Roar",
                pp: 16,
                priority: 0,
                boosts: {
                        atk: -1,
                        spa: -1
                },
                secondary: false,
                target: "normal",
                type: "Fire"
        },
        "fairywind": {
                num: 1037,
                accuracy: 100,
                basePower: 85,
                category: "Special",
                desc: "Deals massive damage to all adjacent foes.",
                shortDesc: "Damages all adjacent foes.",
                id: "fairywind",
                name: "Fairy Wind",
                pp: 25,
                priority: 0,
                secondary: false,
                target: "allAdjacentFoes",
                type: "Fairy"
        },
        "topsyturvy": {
                num: 1038,
                accuracy: 100,
                basePower: 0,
                category: "Status",
                desc: "Reverses all stat modifications that the foe has.",
                shortDesc: "Reverses the foe's stat changes.",
                id: "topsyturvy",
                name: "Topsy-Turvy",
                pp: 16,
                priority: 0,
                onHit: function(target) {
                         for (var i in target.boosts) {
                               target.boosts[i] = target.boosts[i] * (-1);
                        }
                        this.add('-reverseboost', target, '[from] move: Topsy-Turvy');
                },
                secondary: false,
                target: "normal",
                type: "Fairy"
        },
        "drainingkiss": {
                num: 1039,
                accuracy: 100,
                basePower: 80,
                category: "Special",
                desc: "Deals damage to one adjacent target. The user recovers half of the HP lost by the target, rounded up. If Big Root is held by the user, the HP recovered is 1.3x normal, rounded half down.",
                shortDesc: "User recovers 50% of the damage dealt.",
                id: "drainingkiss",
                name: "Draining Kiss",
                pp: 16,
                priority: 0,
                drain: [1,2],
                secondary: false,
                target: "normal",
                type: "Fairy"
        },
        "geomancy": {
                num: 1040,
                accuracy: 100,
                basePower: 130,
                category: "Special",
                desc: "Deal massive damage to one adjacent target.",
                shortDesc: "No additional effect.",
                id: "geomancy",
                name: "Geomancy",
                pp: 16,
                priority: 0,
		self: {
			boosts: {
				spa: -1,
                                spd: -1
			}
		},
                secondary: false,
                target: "all",
                type: "Fairy"
        },
        "oblivionwing": {
                num: 1041,
                accuracy: 100,
                basePower: 130,
                category: "Special",
                desc: "Deal massive damage to one adjacent target.",
                shortDesc: "No additional effect.",
                id: "oblivionwing",
                name: "Oblivion Wing",
                pp: 16,
        	priority: 0,
		self: {
			boosts: {
				spa: -1,
                                spd: -1
			}
		},
                secondary: false,
                target: "all",
                type: "Dark"
	}
};
<|endoftext|>


<commit_before>let SpellCheckTask;
let idCounter = 0;

module.exports = SpellCheckTask = (function () {
    SpellCheckTask = class SpellCheckTask {
        static initClass() {
            this.handler = null;
            this.jobs = [];
        }

        constructor(manager) {
            this.manager = manager;
            this.id = idCounter++;
        }

        terminate() {
            return this.constructor.removeFromArray(
                this.constructor.jobs,
                (j) => j.args.id === this.id
            );
        }

        start(editor, onDidSpellCheck) {
            // Figure out the paths since we need that for checkers that are project-specific.
            const buffer = editor.getBuffer();
            let projectPath = null;
            let relativePath = null;

            if (buffer != null && buffer.file && buffer.file.path) {
                [projectPath, relativePath] = atom.project.relativizePath(
                    buffer.file.path
                );
            }

            // Remove old jobs for this SpellCheckTask from the shared jobs list.
            this.constructor.removeFromArray(
                this.constructor.jobs,
                (j) => j.args.id === this.id
            );

            // Create an job that contains everything we'll need to do the work.
            const job = {
                manager: this.manager,
                callbacks: [onDidSpellCheck],
                editorId: editor.id,
                args: {
                    id: this.id,
                    projectPath,
                    relativePath,
                    text: buffer.getText(),
                },
            };

            // If we already have a job for this work piggy-back on it with our callback.
            if (this.constructor.piggybackExistingJob(job)) {
                return;
            }

            // Do the work now if not busy or queue it for later.
            this.constructor.jobs.unshift(job);
            if (this.constructor.jobs.length === 1) {
                return this.constructor.startNextJob();
            }
        }

        static piggybackExistingJob(newJob) {
            if (this.jobs.length > 0) {
                for (let job of this.jobs) {
                    if (this.isDuplicateRequest(job, newJob)) {
                        job.callbacks = job.callbacks.concat(newJob.callbacks);
                        return true;
                    }
                }
            }
            return false;
        }

        static isDuplicateRequest(a, b) {
            return (
                a.args.projectPath === b.args.projectPath &&
                a.args.relativePath === b.args.relativePath
            );
        }

        static removeFromArray(array, predicate) {
            if (array.length > 0) {
                for (let i = 0; i < array.length; i++) {
                    if (predicate(array[i])) {
                        const found = array[i];
                        array.splice(i, 1);
                        return found;
                    }
                }
            }
        }

        static startNextJob() {
            const activeEditor = atom.workspace.getActiveTextEditor();
            const activeEditorId = activeEditor.id;
            const job =
                this.jobs.find((j) => j.editorId === activeEditorId) ||
                this.jobs[0];

            return job.manager
                .check(job.args, job.args.text)
                .then((results) => {
                    this.removeFromArray(
                        this.jobs,
                        (j) => j.args.id === job.args.id
                    );
                    for (let callback of job.callbacks) {
                        callback(results.misspellings);
                    }

                    if (this.jobs.length > 0) {
                        return this.startNextJob();
                    }
                });
        }

        static clear() {
            return (this.jobs = []);
        }
    };
    SpellCheckTask.initClass();
    return SpellCheckTask;
})();
<commit_msg>check for null<commit_after>let SpellCheckTask;
let idCounter = 0;

module.exports = SpellCheckTask = (function () {
    SpellCheckTask = class SpellCheckTask {
        static initClass() {
            this.handler = null;
            this.jobs = [];
        }

        constructor(manager) {
            this.manager = manager;
            this.id = idCounter++;
        }

        terminate() {
            return this.constructor.removeFromArray(
                this.constructor.jobs,
                (j) => j.args.id === this.id
            );
        }

        start(editor, onDidSpellCheck) {
            // Figure out the paths since we need that for checkers that are project-specific.
            const buffer = editor.getBuffer();
            let projectPath = null;
            let relativePath = null;

            if (buffer != null && buffer.file && buffer.file.path) {
                [projectPath, relativePath] = atom.project.relativizePath(
                    buffer.file.path
                );
            }

            // Remove old jobs for this SpellCheckTask from the shared jobs list.
            this.constructor.removeFromArray(
                this.constructor.jobs,
                (j) => j.args.id === this.id
            );

            // Create an job that contains everything we'll need to do the work.
            const job = {
                manager: this.manager,
                callbacks: [onDidSpellCheck],
                editorId: editor.id,
                args: {
                    id: this.id,
                    projectPath,
                    relativePath,
                    text: buffer.getText(),
                },
            };

            // If we already have a job for this work piggy-back on it with our callback.
            if (this.constructor.piggybackExistingJob(job)) {
                return;
            }

            // Do the work now if not busy or queue it for later.
            this.constructor.jobs.unshift(job);
            if (this.constructor.jobs.length === 1) {
                return this.constructor.startNextJob();
            }
        }

        static piggybackExistingJob(newJob) {
            if (this.jobs.length > 0) {
                for (let job of this.jobs) {
                    if (this.isDuplicateRequest(job, newJob)) {
                        job.callbacks = job.callbacks.concat(newJob.callbacks);
                        return true;
                    }
                }
            }
            return false;
        }

        static isDuplicateRequest(a, b) {
            return (
                a.args.projectPath === b.args.projectPath &&
                a.args.relativePath === b.args.relativePath
            );
        }

        static removeFromArray(array, predicate) {
            if (array.length > 0) {
                for (let i = 0; i < array.length; i++) {
                    if (predicate(array[i])) {
                        const found = array[i];
                        array.splice(i, 1);
                        return found;
                    }
                }
            }
        }

        static startNextJob() {
            const activeEditor = atom.workspace.getActiveTextEditor();
            if (!activeEditor) return;

            const activeEditorId = activeEditor.id;
            const job =
                this.jobs.find((j) => j.editorId === activeEditorId) ||
                this.jobs[0];

            return job.manager
                .check(job.args, job.args.text)
                .then((results) => {
                    this.removeFromArray(
                        this.jobs,
                        (j) => j.args.id === job.args.id
                    );
                    for (let callback of job.callbacks) {
                        callback(results.misspellings);
                    }

                    if (this.jobs.length > 0) {
                        return this.startNextJob();
                    }
                });
        }

        static clear() {
            return (this.jobs = []);
        }
    };
    SpellCheckTask.initClass();
    return SpellCheckTask;
})();
<|endoftext|>


<commit_before>'use strict'
const debug = require('debug')('webd2-api:relations')
const { find, filter } = require('lodash')

const registry = require('../models/registry')
const foreignKey = require('../sql-builder/foreign-key')
const {
  getHasManyDescriptors,
  getBelongsToDescriptors
} = require('../sql-builder/get-rel-descriptors')

class Relations {
  constructor (modelName, modelSchema, registryMock) {
    if (modelName == null) throw new TypeError('modelName is undefined')
    if (modelSchema == null) throw new TypeError('modelSchema is undefined')
    if (typeof modelSchema !== 'object') throw new TypeError('modelSchema should be an object')
    if (modelSchema.tableName == null) throw new TypeError('modelSchema tableName is undefined')

    debug(`Relations for '${modelName}' model`)

    this.modelName = modelName
    this.modelSchema = modelSchema

    this.registry = registryMock || registry

    this.fkAsForHasMany = foreignKey(modelName)

    this.belongsToDescriptors = getBelongsToDescriptors(modelSchema)
    this.hasManyDescriptors = getHasManyDescriptors(modelSchema)
  }

  getAttributesOfRelations () {
    const result = {}

    this.belongsToDescriptors.forEach((rel) => {
      const relationModel = this.registry.model(rel.relationModelName)
      if (!relationModel) throw new Error(`there is no registered '${rel.relationModelName}' model`)
      result[rel.modelFieldName] = relationModel.attributesSerialize
    })

    this.hasManyDescriptors.forEach((rel) => {
      const relationModel = this.registry.model(rel.relationModelName)
      if (!relationModel) throw new Error(`there is no registered '${rel.relationModelName}' model`)
      result[rel.modelFieldName] = relationModel.attributesSerialize
    })

    return result
  }

  _embedHasMany (parentRows, relationsData) {
    debug(`${this.modelName}:_embedHasMany`)

    return parentRows.map((parentRow) => {
      relationsData.forEach((rel) => {
        parentRow[rel.modelFieldName] = filter(rel.rows, [this.fkAsForHasMany, parentRow.id])
        .map((relRow) => {
          relRow[rel.parentModelFieldName] = { id: parentRow.id } // reverse connection
          delete relRow[this.fkAsForHasMany]
          return relRow
        })
      })
      return parentRow
    })
  }

  _embedBelongsTo (parentRows, relationsData) {
    debug(`${this.modelName}:_embedBelongsTo`)

    return parentRows.map((parentRow) => {
      relationsData.forEach((rel) => {
        if (rel.rows) {
          parentRow[rel.modelFieldName] = find(rel.rows, { id: parentRow[rel.fkAs] })
        } else {
          parentRow[rel.modelFieldName] = { id: parentRow[rel.fkAs] }
        }
        delete parentRow[rel.fkAs]
      })
      return parentRow
    })
  }

  justTransformIDs (parentRows) {
    debug(`${this.modelName}:justTransformIDs`)

    return parentRows.map((parentRow) => {
      this.belongsToDescriptors.forEach((rel) => {
        parentRow[rel.modelFieldName] = { id: parentRow[rel.fkAs] }
        delete parentRow[rel.fkAs]
      })
      return parentRow
    })
  }

  _fetchHasMany (parentWhere) {
    debug(`${this.modelName}:_fetchHasMany`)

    return Promise.all(this.hasManyDescriptors.map((relDescriptor) => {
      const relationModel = this.registry.model(relDescriptor.relationModelName)

      if (!relationModel) {
        return Promise.reject(
          new Error(`_fetchHasMany: there is no registered '${relDescriptor.relationModelName}' model`)
        )
      }

      const parentModelFieldName = findModelFieldName(this.modelName, relationModel.schema)

      return relationModel.selectMany({
        whereIn: {
          relationFkName: relDescriptor.fkField,
          parentIdFieldName: this.modelSchema.id || 'id',
          parentTableName: this.modelSchema.tableName,
          parentWhere
        }
      })
      .then((castRows) => {
        return {
          modelFieldName: relDescriptor.modelFieldName,
          parentModelFieldName,
          rows: castRows
        }
      })
    }))
  }

  _fetchBelongsTo (parentWhere) {
    debug(`${this.modelName}:_fetchBelongsTo`)

    return Promise.all(this.belongsToDescriptors.map((relDescriptor) => {
      const relationModel = this.registry.model(relDescriptor.relationModelName)

      if (!relationModel) {
        return Promise.reject(
          new Error(`_fetchBelongsTo: there is no registered '${relDescriptor.relationModelName}' model`)
        )
      }

      return relationModel.selectMany({
        whereIn: {
          parentFkName: relDescriptor.fkField,
          parentTableName: this.modelSchema.tableName,
          parentWhere
        }
      })
      .then((castRows) => {
        return {
          modelFieldName: relDescriptor.modelFieldName,
          fkAs: relDescriptor.fkAs,
          rows: castRows
        }
      })
    }))
  }

  fetchAndEmbed (parentRows, parentWhere) {
    return this._fetchBelongsTo(parentWhere)
    .then((relationsData) => this._embedBelongsTo(parentRows, relationsData))
    .then(
      (partialResult) => this._fetchHasMany(parentWhere)
      .then((relationsData) => [partialResult, relationsData])
    )
    .then(([partialResult, relationsData]) => this._embedHasMany(partialResult, relationsData))
  }
}

function findModelFieldName (modelName, relModelSchema) {
  const belongsToDescriptors = getBelongsToDescriptors(relModelSchema)
  const relDescriptor = find(belongsToDescriptors, ['relationModelName', modelName])

  if (!relDescriptor) throw new Error(`there is no belongsTo descriptor for '${modelName}'`)

  return relDescriptor.modelFieldName
}

Relations.findModelFieldName = findModelFieldName

module.exports = Relations
<commit_msg>relations.getAttributesOfRelations() uses model.asRelationAttributesSerialize<commit_after>'use strict'
const debug = require('debug')('webd2-api:relations')
const { find, filter } = require('lodash')

const registry = require('../models/registry')
const foreignKey = require('../sql-builder/foreign-key')
const {
  getHasManyDescriptors,
  getBelongsToDescriptors
} = require('../sql-builder/get-rel-descriptors')

class Relations {
  constructor (modelName, modelSchema, registryMock) {
    if (modelName == null) throw new TypeError('modelName is undefined')
    if (modelSchema == null) throw new TypeError('modelSchema is undefined')
    if (typeof modelSchema !== 'object') throw new TypeError('modelSchema should be an object')
    if (modelSchema.tableName == null) throw new TypeError('modelSchema tableName is undefined')

    debug(`Relations for '${modelName}' model`)

    this.modelName = modelName
    this.modelSchema = modelSchema

    this.registry = registryMock || registry

    this.fkAsForHasMany = foreignKey(modelName)

    this.belongsToDescriptors = getBelongsToDescriptors(modelSchema)
    this.hasManyDescriptors = getHasManyDescriptors(modelSchema)
  }

  getAttributesOfRelations () {
    const result = {}

    this.belongsToDescriptors.forEach((rel) => {
      const relationModel = this.registry.model(rel.relationModelName)
      if (!relationModel) throw new Error(`there is no registered '${rel.relationModelName}' model`)
      result[rel.modelFieldName] = relationModel.asRelationAttributesSerialize
    })

    this.hasManyDescriptors.forEach((rel) => {
      const relationModel = this.registry.model(rel.relationModelName)
      if (!relationModel) throw new Error(`there is no registered '${rel.relationModelName}' model`)
      result[rel.modelFieldName] = relationModel.asRelationAttributesSerialize
    })

    return result
  }

  _embedHasMany (parentRows, relationsData) {
    debug(`${this.modelName}:_embedHasMany`)

    return parentRows.map((parentRow) => {
      relationsData.forEach((rel) => {
        parentRow[rel.modelFieldName] = filter(rel.rows, [this.fkAsForHasMany, parentRow.id])
        .map((relRow) => {
          relRow[rel.parentModelFieldName] = { id: parentRow.id } // reverse connection
          delete relRow[this.fkAsForHasMany]
          return relRow
        })
      })
      return parentRow
    })
  }

  _embedBelongsTo (parentRows, relationsData) {
    debug(`${this.modelName}:_embedBelongsTo`)

    return parentRows.map((parentRow) => {
      relationsData.forEach((rel) => {
        if (rel.rows) {
          parentRow[rel.modelFieldName] = find(rel.rows, { id: parentRow[rel.fkAs] })
        } else {
          parentRow[rel.modelFieldName] = { id: parentRow[rel.fkAs] }
        }
        delete parentRow[rel.fkAs]
      })
      return parentRow
    })
  }

  justTransformIDs (parentRows) {
    debug(`${this.modelName}:justTransformIDs`)

    return parentRows.map((parentRow) => {
      this.belongsToDescriptors.forEach((rel) => {
        parentRow[rel.modelFieldName] = { id: parentRow[rel.fkAs] }
        delete parentRow[rel.fkAs]
      })
      return parentRow
    })
  }

  _fetchHasMany (parentWhere) {
    debug(`${this.modelName}:_fetchHasMany`)

    return Promise.all(this.hasManyDescriptors.map((relDescriptor) => {
      const relationModel = this.registry.model(relDescriptor.relationModelName)

      if (!relationModel) {
        return Promise.reject(
          new Error(`_fetchHasMany: there is no registered '${relDescriptor.relationModelName}' model`)
        )
      }

      const parentModelFieldName = findModelFieldName(this.modelName, relationModel.schema)

      return relationModel.selectMany({
        whereIn: {
          relationFkName: relDescriptor.fkField,
          parentIdFieldName: this.modelSchema.id || 'id',
          parentTableName: this.modelSchema.tableName,
          parentWhere
        }
      })
      .then((castRows) => {
        return {
          modelFieldName: relDescriptor.modelFieldName,
          parentModelFieldName,
          rows: castRows
        }
      })
    }))
  }

  _fetchBelongsTo (parentWhere) {
    debug(`${this.modelName}:_fetchBelongsTo`)

    return Promise.all(this.belongsToDescriptors.map((relDescriptor) => {
      const relationModel = this.registry.model(relDescriptor.relationModelName)

      if (!relationModel) {
        return Promise.reject(
          new Error(`_fetchBelongsTo: there is no registered '${relDescriptor.relationModelName}' model`)
        )
      }

      return relationModel.selectMany({
        whereIn: {
          parentFkName: relDescriptor.fkField,
          parentTableName: this.modelSchema.tableName,
          parentWhere
        }
      })
      .then((castRows) => {
        return {
          modelFieldName: relDescriptor.modelFieldName,
          fkAs: relDescriptor.fkAs,
          rows: castRows
        }
      })
    }))
  }

  fetchAndEmbed (parentRows, parentWhere) {
    return this._fetchBelongsTo(parentWhere)
    .then((relationsData) => this._embedBelongsTo(parentRows, relationsData))
    .then(
      (partialResult) => this._fetchHasMany(parentWhere)
      .then((relationsData) => [partialResult, relationsData])
    )
    .then(([partialResult, relationsData]) => this._embedHasMany(partialResult, relationsData))
  }
}

function findModelFieldName (modelName, relModelSchema) {
  const belongsToDescriptors = getBelongsToDescriptors(relModelSchema)
  const relDescriptor = find(belongsToDescriptors, ['relationModelName', modelName])

  if (!relDescriptor) throw new Error(`there is no belongsTo descriptor for '${modelName}'`)

  return relDescriptor.modelFieldName
}

Relations.findModelFieldName = findModelFieldName

module.exports = Relations
<|endoftext|>


<commit_before><commit_msg>Change test case. Expect to throw error.<commit_after><|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({
			'name': point.name,
			'pos': point.pos,
			'module': point.module,
			'args': point.args
		}));
	}
	points=undefined;
	delete point,points;
	var _=this;
	link.on('message',function(msg){
		msg=JSON.parse(msg);
		switch(msg.action){
			case 'move':
				if(Number.isNaN(msg.pos[0]) || Number.isNaN(msg.pos[1]) || !Array.isArray(msg.args))
					return;
				map.movePoint(msg.name,msg.pos,msg.module,msg.args);
			break;
			case 'delete':
				map.deletePoint(msg.name);
			break;
		}
	});
	link.on('close',function(){
		_.lastReadTime=new Date().getTime();
		_.link.splice(_.links.indexOf(link),1);
		_=link=undefined;
		delete _,link;
	});
}
Map.prototype.movePoint=function(name,pos,module,args){
	var hashName=md5(name);
	var point
	if(!(point=this.points[hashName])){
		point=this.points[hashName]={'name':name};
	}
	pos!=undefined && point.pos=pos;
	module!=undefined && point.module=module;
	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 name=point.name;
				if(this.points[name] || this.deletedPoints.indexOf(name)>=0)
					return;
				this.points[name]={
					'id': point.id,
					'pos': [point.posX,point.posY],
					'module': point.module,
					'args': JSON.parse(point.args)
				};
			},map);
		}
	);
	return map;
}
var writeDB=(function(){
	var createMap=function(hashName,callback){
		var map=maps[hashName];
		DB.query('INSERT INTO `maps` (`name`,`lastReadTime`) VALUE (?)',[hashName,map.lastReadTime],function(err,result){
			if(err){
				console.log('[mapPoint] 無法建立地圖 hashName=%s',hashName);
				return;
			}
			map.id=result.insertId;
			callback(hashName);
		});
	};
	var writeMap=function(hashName){
		var map=maps[hashName];
		var mapId=map.id;
		DB.query(
			'UPDATE `maps` SET `lastReadTime`=? WHERE `id`=?',
			[map.lastReadTime,mapId],
			function(err){
				if(err){
					console.log('[mapPoint] 地圖最後讀取時間更新失敗 id=%d',mapId);
				}
			}
		);
		map.updatedPoints.forEach(function(key,index,o){
			var point=this[key];
			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);
						}
					}
				);
			else
				DB.query(
					'INSERT INTO `points` (`mapId`,`posX`,`posY`,`module`,`args`) VALUE (?)',
					[mapId,point.pos[0],point.pos[1],point.module,JSON.stringify(point.args),point.id],
					function(err,result){
						if(err){
							console.log('[mapPoint] 新增標記點失敗 map=%s,name=%s',hashName,point.name);
							return;
						}
						point.id=result.insertId;
					}
				);
		},map.points);
		if(map.deletedPoints.length){
			var list=map.deletedPoints;
			DB.query('DELETE FROM `point` WHERE `id` IN (?)',list,function(err){
				if(err){
					console.log('[mapPoint] 刪除標記點失敗 id=%s',list.join(','));
				}
				list=undefined;
				delete list;
			});
			map.deletedPoints=[];
		}

	};
	return function(){
		var outTime=new Date().getTime()-cacheTime;
		var hashName,map;
		for(hashName in maps){
			map=maps[hashName];
			if(map.updatedPoints.length || map.deletedPoints.length){
				if(map.id)
					writeMap(hashName);
				else
					createMap(hashName,writeMap);
			}
			if(map.link.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);
//退出前寫出記憶體
process.on('exit',function(){
	for(var hashName in maps){
		maps[hashName].links.forEach(function(link){
			link.close();
		});
	}
	writeDB();
});

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.substring(1,url.length);
			getMap(name).join(link);
		});
	}
};<commit_msg>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({
			'name': point.name,
			'pos': point.pos,
			'module': point.module,
			'args': point.args
		}));
	}
	points=undefined;
	delete point,points;
	var _=this;
	link.on('message',function(msg){
		msg=JSON.parse(msg);
		switch(msg.action){
			case 'move':
				if(Number.isNaN(msg.pos[0]) || Number.isNaN(msg.pos[1]) || !Array.isArray(msg.args))
					return;
				map.movePoint(msg.name,msg.pos,msg.module,msg.args);
			break;
			case 'delete':
				map.deletePoint(msg.name);
			break;
		}
	});
	link.on('close',function(){
		_.lastReadTime=new Date().getTime();
		_.link.splice(_.links.indexOf(link),1);
		_=link=undefined;
		delete _,link;
	});
}
Map.prototype.movePoint=function(name,pos,module,args){
	var hashName=md5(name);
	var point
	if(!(point=this.points[hashName])){
		point=this.points[hashName]={'name':name};
	}
	pos!=undefined && point.pos=pos;
	module!=undefined && point.module=module;
	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 name=point.name;
				if(this.points[name] || this.deletedPoints.indexOf(name)>=0)
					return;
				this.points[name]={
					'id': point.id,
					'pos': [point.posX,point.posY],
					'module': point.module,
					'args': JSON.parse(point.args)
				};
			},map);
		}
	);
	return map;
}
var writeDB=(function(){
	var createMap=function(hashName,callback){
		var map=maps[hashName];
		DB.query('INSERT INTO `maps` (`name`,`lastReadTime`) VALUE (?)',[hashName,map.lastReadTime],function(err,result){
			if(err){
				console.log('[mapPoint] 無法建立地圖 hashName=%s',hashName);
				return;
			}
			map.id=result.insertId;
			callback(hashName);
		});
	};
	var writeMap=function(hashName){
		var map=maps[hashName];
		var mapId=map.id;
		DB.query(
			'UPDATE `maps` SET `lastReadTime`=? WHERE `id`=?',
			[map.lastReadTime,mapId],
			function(err){
				if(err){
					console.log('[mapPoint] 地圖最後讀取時間更新失敗 id=%d',mapId);
				}
			}
		);
		map.updatedPoints.forEach(function(key,index,o){
			var point=this[key];
			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);
						}
					}
				);
			else
				DB.query(
					'INSERT INTO `points` (`mapId`,`posX`,`posY`,`module`,`args`) VALUE (?)',
					[mapId,point.pos[0],point.pos[1],point.module,JSON.stringify(point.args),point.id],
					function(err,result){
						if(err){
							console.log('[mapPoint] 新增標記點失敗 map=%s,name=%s',hashName,point.name);
							return;
						}
						point.id=result.insertId;
					}
				);
		},map.points);
		if(map.deletedPoints.length){
			var list=map.deletedPoints;
			DB.query('DELETE FROM `point` WHERE `id` IN (?)',list,function(err){
				if(err){
					console.log('[mapPoint] 刪除標記點失敗 id=%s',list.join(','));
				}
				list=undefined;
				delete list;
			});
			map.deletedPoints=[];
		}

	};
	return function(){
		var outTime=new Date().getTime()-cacheTime;
		var hashName,map;
		for(hashName in maps){
			map=maps[hashName];
			if(map.updatedPoints.length || map.deletedPoints.length){
				if(map.id)
					writeMap(hashName);
				else
					createMap(hashName,writeMap);
			}
			if(map.link.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);
//退出前寫出記憶體
process.on('exit',function(){
	for(var hashName in maps){
		maps[hashName].links.forEach(function(link){
			link.close();
		});
	}
	writeDB();
});

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);
		});
	}
};<|endoftext|>


<commit_before>"use strict"
var debug = require('debug')('xcleague:httphelpers.js');
var merge = require('merge');

function HttpHelpers() {

}

HttpHelpers.prototype.forceHttps = function(req,res,next) {

  // Skip this if it isn't production
  if (process.env.NODE_ENV !== 'production') {
    return next();
  }

  // Check the x-forwarded-proto for http and if so redirect to https
  if (req.headers['x-forwarded-proto'] === 'http') {
    res.redirect('https://xcleague-nwxc.rhcloud.com' + req.path);
  } else {
    return next();
  }
}

HttpHelpers.prototype.forceHttp = function(req,res,next) {

  console.log(req.headers);

  // Skip this if it isn't production
  if (process.env.NODE_ENV !== 'production') {
    return next();
  }

  // If it is production and not www.nw-xc.com redirect
  if (req.headers['host'] !== 'www.nw-xc.com') {
    return res.redirect('http://www.nw-xc.com' + req.path);
  }

  return next();
}

HttpHelpers.prototype.restrict = function(req, res, next) {
  debug("Starting the restrict function");

  if (remembermeIsStolen(req)) {
    req.rememberme.destroy();
  }

  if (typeof req.session !== 'undefined' && req.session && req.session.user) {
    debug("restrict session is valid");
    return next();
  }

  debug("restrict session is not valid");

  if (req.params.format == 'json') {
    var codeToRespond = 401;
    debug("respond returning JSON with code="+codeToRespond);
    return res.json.call(res, {error: "Session is not valid"}, codeToRespond);
  }

  res.redirect('/login');

}

HttpHelpers.prototype.respond = function(req, res, htmlPartial, dataObj, statusCode) {
  res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
  dataObj = merge(merge({}, req.body), dataObj);
  dataObj = this.addStandardResponseFields(dataObj, req.session);

  if (!req.accepts('html') || req.params.format == 'json') {
    var codeToRespond = statusCode || 200;
    _logger.trace("respond returning JSON >" + JSON.stringify(dataObj) + "< with code="+codeToRespond);
    return res.status(codeToRespond).json(dataObj);
  }

  var codeToRespond = statusCode || 200;
  res.status(codeToRespond);

  _logger.info("respond returning HTML with data >" + JSON.stringify(dataObj) + "< and code="+codeToRespond);
  return res.render.call(res, htmlPartial, dataObj);

}

HttpHelpers.prototype.respondError = function(req,res,htmlPartial, dataObj, x, statusCode) {
  dataObj = merge(
    {
      error: true,
      updated: false
    },
    dataObj
  );

  if (!req.accepts('html') || req.params.format == 'json') {
      var codeToRespond = statusCode || 200;
      _logger.trace("respond returning JSON >" + dataObj + "< with code="+codeToRespond);
      return res.json.call(res, dataObj, codeToRespond);
  }

  if (typeof x !== 'number') statusCode = x, x = undefined;
  if (x) return exports.respond(req, res, htmlPartial, merge(dataObj, x), statusCode || 400);
  return exports.respond(req, res, htmlPartial, dataObj, statusCode || 400);
}

HttpHelpers.prototype.addStandardResponseFields = function(dataObject,sessionObj) {

    try {
        if (typeof sessionObj !== "undefined") {
            var loggedInUser = merge({},sessionObj.user);
            dataObject.liuser = exports.buildAcceptableUserObject(loggedInUser);
        } else {
            return dataObject;
        }

    } catch (err) {
        _logger.crit("addStandardResponseFields err="+err+err.stack);
    }

    return dataObject;
};

module.exports = new HttpHelpers();<commit_msg>Fixing a logic flaw<commit_after>"use strict"
var debug = require('debug')('xcleague:httphelpers.js');
var merge = require('merge');

function HttpHelpers() {

}

HttpHelpers.prototype.forceHttps = function(req,res,next) {

  // Skip this if it isn't production
  if (process.env.NODE_ENV !== 'production') {
    return next();
  }

  // Check the x-forwarded-proto for http and if so redirect to https
  if (req.headers['x-forwarded-proto'] === 'http') {
    res.redirect('https://xcleague-nwxc.rhcloud.com' + req.path);
  } else {
    return next();
  }
}

HttpHelpers.prototype.forceHttp = function(req,res,next) {

  console.log(req.headers);

  // Skip this if it isn't production
  if (process.env.NODE_ENV !== 'production') {
    return next();
  }

  // If it is production and not www.nw-xc.com redirect
  if (req.headers['x-forwarded-proto'] === 'https') {
    return res.redirect('http://www.nw-xc.com' + req.path);
  }

  return next();
}

HttpHelpers.prototype.restrict = function(req, res, next) {
  debug("Starting the restrict function");

  if (remembermeIsStolen(req)) {
    req.rememberme.destroy();
  }

  if (typeof req.session !== 'undefined' && req.session && req.session.user) {
    debug("restrict session is valid");
    return next();
  }

  debug("restrict session is not valid");

  if (req.params.format == 'json') {
    var codeToRespond = 401;
    debug("respond returning JSON with code="+codeToRespond);
    return res.json.call(res, {error: "Session is not valid"}, codeToRespond);
  }

  res.redirect('/login');

}

HttpHelpers.prototype.respond = function(req, res, htmlPartial, dataObj, statusCode) {
  res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
  dataObj = merge(merge({}, req.body), dataObj);
  dataObj = this.addStandardResponseFields(dataObj, req.session);

  if (!req.accepts('html') || req.params.format == 'json') {
    var codeToRespond = statusCode || 200;
    _logger.trace("respond returning JSON >" + JSON.stringify(dataObj) + "< with code="+codeToRespond);
    return res.status(codeToRespond).json(dataObj);
  }

  var codeToRespond = statusCode || 200;
  res.status(codeToRespond);

  _logger.info("respond returning HTML with data >" + JSON.stringify(dataObj) + "< and code="+codeToRespond);
  return res.render.call(res, htmlPartial, dataObj);

}

HttpHelpers.prototype.respondError = function(req,res,htmlPartial, dataObj, x, statusCode) {
  dataObj = merge(
    {
      error: true,
      updated: false
    },
    dataObj
  );

  if (!req.accepts('html') || req.params.format == 'json') {
      var codeToRespond = statusCode || 200;
      _logger.trace("respond returning JSON >" + dataObj + "< with code="+codeToRespond);
      return res.json.call(res, dataObj, codeToRespond);
  }

  if (typeof x !== 'number') statusCode = x, x = undefined;
  if (x) return exports.respond(req, res, htmlPartial, merge(dataObj, x), statusCode || 400);
  return exports.respond(req, res, htmlPartial, dataObj, statusCode || 400);
}

HttpHelpers.prototype.addStandardResponseFields = function(dataObject,sessionObj) {

    try {
        if (typeof sessionObj !== "undefined") {
            var loggedInUser = merge({},sessionObj.user);
            dataObject.liuser = exports.buildAcceptableUserObject(loggedInUser);
        } else {
            return dataObject;
        }

    } catch (err) {
        _logger.crit("addStandardResponseFields err="+err+err.stack);
    }

    return dataObject;
};

module.exports = new HttpHelpers();<|endoftext|>


<commit_before>/**
 *  Copyright (c) 2010 Marat Nepomnyashy    maratbn@gmail
 *  All rights reserved.
 *
 *  Module:         h5ile.js
 *
 *  Description:    JavaScript helper utility library for the HTML5 File API
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *      * Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 *      * Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *      * Neither the name of the <organization> nor the
 *        names of its contributors may be used to endorse or promote products
 *        derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 *  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 */

window.h5ile = {
    /**
     *  Creates a visible HTML form field file input tag, prepares it to load
     *  files, and appends it as a child to the DOM element with the DOM id
     *  specified.
     *
     *  @param  idParentElement         DOM id of the parent element
     *
     *  @param  params                  JSON structure with file processing
     *                                  parameters.
     *
     *          params.loadtext         Sub-field for text loading settings.
     *
     *          params.loadtext.onloadend
     *                                  Callback function for when a file has
     *                                  been loaded as regular text.
     *                                  1st parameter:  'File' object read.
     *                                  2nd parameter:  'FileReader' object.
     *
     *  @returns the created tag.
     */
    createVisibleFileInput: function(idParentElement, params) {
        var elParent = document.getElementById(idParentElement);
        if (!elParent) throw new Error("h5ile: Invalid parent element");

        var elInput = document.createElement('input');
        elInput.setAttribute('type', 'file');

        this.prepareFileInput(elInput, params);

        elParent.appendChild(elInput);

        return elInput;
    },

    /**
     *  @returns  Boolean true if the platform supports HTML 5 File API.
     */
    isFileAPISupported: function() {
        return window.File && window.FileReader ? true : false;
    },

    /**
     *  Prepares the HTML form field file input tag element specified to load
     *  files.
     *
     *  @param  elInput                 Reference to the form field file input
     *                                  tag element to prepare.
     *
     *  @param  params                  JSON structure with file processing
     *                                  parameters.
     *
     *          params.loadtext         Sub-field for text loading settings.
     *
     *          params.loadtext.onloadend
     *                                  Callback function for when a file has
     *                                  been loaded as regular text.
     *                                  1st parameter:  'File' object read.
     *                                  2nd parameter:  'FileReader' object.
     *
     */
    prepareFileInput: function(elInput, params) {

        // Based on:
        // https://developer.mozilla.org/en/using_files_from_web_applications

        var _processChange = function() {
                if (!this.files) {
                    throw new Error("h5ile:  Expected 'FileList' object, but"
                                    + " did not get any");
                }

                for (var i = 0; i < this.files.length; i++) {
                    var file = this.files[i];
                    if (!file) continue;

                    if (params &&
                        params.loadtext &&
                        params.loadtext.onloadend) {

                        var reader = new FileReader();

                        reader.onabort = function() {
                                alert("h5ile:  Aborted text loading of file '" + file.name + "'.");
                            }
                        reader.onloadend = function() {
                                params.loadtext.onloadend(file, reader);
                            }
                        reader.readAsText(file);
                    }
                }
            }

        if (elInput.addEventListener) {
            elInput.addEventListener('change', _processChange, false);
        } else if (elInput.attachEvent) {
            elInput.attachEvent('change', _processChange);
        } else {
            throw new Error("h5ile:  Expected either 'addEventListener' or 'attachEvent'.");
        }
    },

    /**
     *  Reads text from the file specified, and splits it into lines.
     *
     *  @returns Object with info on lines.
     */
    splitIntoLines: function(file_reader) {
        var arrSplit =  file_reader &&
                        file_reader.result &&
                        file_reader.result.split(/(\r\n|\r|\n)/);
        // The 'arrSplit' variable is an array where the line text and the
        // line delimiters are at separate indices.  Need to process that into
        // another array where the line delimiters are appended to the ends of
        // their associated lines.
        var arrLines = [];
        var strLine = "";
        for (var i = 0; i < arrSplit.length; i ++) {
            strLine += arrSplit[i];
            if (strLine.match(/\r\n|\r|\n/)) {
                arrLines.push(strLine);
                strLine = "";
            }
        }
        if (strLine) arrLines.push(strLine);

        function _getLine(indexLine) {
                return  arrLines &&
                        arrLines.length > indexLine &&
                        arrLines[indexLine] || "";
            }

        function _getLineStripped(indexLine) {
                var strLine = _getLine(indexLine);
                var arrBreakdownForLine = strLine &&
                                            strLine.match(/^\s*(.+\S+)\s*$/);
                return arrBreakdownForLine &&
                        arrBreakdownForLine.length == 2 &&
                        arrBreakdownForLine[1] || null;
            }

        var _arrLineTokens = [];

        function _insureTokenLineExists(indexLine) {
                if (_arrLineTokens[indexLine]) return;

                var strLineStripped = _getLineStripped(indexLine);
                _arrLineTokens[indexLine] = strLineStripped && strLineStripped.split(/\s/) || [];
            }

        return {
                /**
                 *  Total number of characters in the text file.
                 */
                totalChars: file_reader &&
                            file_reader.result &&
                            file_reader.result.length || 0,

                /**
                 *  Total number of lines in the text file.
                 *
                 *  The text file is stored line by line rather than in a
                 *  single string, as otherwise a huge file in a huge string
                 *  would overwhelm the browser's JavaScript engine.
                 */
                totalLines: arrLines && arrLines.length || 0,

                /**
                 *  Returns a single line out of the text file, including the
                 *  line termination carriage return character sequence.
                 *
                 *  @param  indexLine       Number  0-based index of the line.
                 */
                getLine: function(indexLine) {
                        return _getLine(indexLine);
                    },

                /**
                 *  Returns a single line out of the text file, stripped of
                 *  the whitespace bounding it.
                 *
                 *  @param  indexLine       Number  0-based index of the line.
                 */
                getLineStripped: function(indexLine) {
                        return _getLineStripped(indexLine);
                    },

                /**
                 *  Returns the total number of non-whitespace tokens on the
                 *  line with the index specified.
                 *
                 *  @param  indexLine       Number  0-based index of the line.
                 */
                getTotalTokensOnLine: function(indexLine) {
                        _insureTokenLineExists(indexLine);
                        return _arrLineTokens[indexLine].length;
                    }
            }
    }
}<commit_msg>Added method 'getTokenOnLine(...)'.<commit_after>/**
 *  Copyright (c) 2010 Marat Nepomnyashy    maratbn@gmail
 *  All rights reserved.
 *
 *  Module:         h5ile.js
 *
 *  Description:    JavaScript helper utility library for the HTML5 File API
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *      * Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 *      * Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *      * Neither the name of the <organization> nor the
 *        names of its contributors may be used to endorse or promote products
 *        derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 *  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 */

window.h5ile = {
    /**
     *  Creates a visible HTML form field file input tag, prepares it to load
     *  files, and appends it as a child to the DOM element with the DOM id
     *  specified.
     *
     *  @param  idParentElement         DOM id of the parent element
     *
     *  @param  params                  JSON structure with file processing
     *                                  parameters.
     *
     *          params.loadtext         Sub-field for text loading settings.
     *
     *          params.loadtext.onloadend
     *                                  Callback function for when a file has
     *                                  been loaded as regular text.
     *                                  1st parameter:  'File' object read.
     *                                  2nd parameter:  'FileReader' object.
     *
     *  @returns the created tag.
     */
    createVisibleFileInput: function(idParentElement, params) {
        var elParent = document.getElementById(idParentElement);
        if (!elParent) throw new Error("h5ile: Invalid parent element");

        var elInput = document.createElement('input');
        elInput.setAttribute('type', 'file');

        this.prepareFileInput(elInput, params);

        elParent.appendChild(elInput);

        return elInput;
    },

    /**
     *  @returns  Boolean true if the platform supports HTML 5 File API.
     */
    isFileAPISupported: function() {
        return window.File && window.FileReader ? true : false;
    },

    /**
     *  Prepares the HTML form field file input tag element specified to load
     *  files.
     *
     *  @param  elInput                 Reference to the form field file input
     *                                  tag element to prepare.
     *
     *  @param  params                  JSON structure with file processing
     *                                  parameters.
     *
     *          params.loadtext         Sub-field for text loading settings.
     *
     *          params.loadtext.onloadend
     *                                  Callback function for when a file has
     *                                  been loaded as regular text.
     *                                  1st parameter:  'File' object read.
     *                                  2nd parameter:  'FileReader' object.
     *
     */
    prepareFileInput: function(elInput, params) {

        // Based on:
        // https://developer.mozilla.org/en/using_files_from_web_applications

        var _processChange = function() {
                if (!this.files) {
                    throw new Error("h5ile:  Expected 'FileList' object, but"
                                    + " did not get any");
                }

                for (var i = 0; i < this.files.length; i++) {
                    var file = this.files[i];
                    if (!file) continue;

                    if (params &&
                        params.loadtext &&
                        params.loadtext.onloadend) {

                        var reader = new FileReader();

                        reader.onabort = function() {
                                alert("h5ile:  Aborted text loading of file '" + file.name + "'.");
                            }
                        reader.onloadend = function() {
                                params.loadtext.onloadend(file, reader);
                            }
                        reader.readAsText(file);
                    }
                }
            }

        if (elInput.addEventListener) {
            elInput.addEventListener('change', _processChange, false);
        } else if (elInput.attachEvent) {
            elInput.attachEvent('change', _processChange);
        } else {
            throw new Error("h5ile:  Expected either 'addEventListener' or 'attachEvent'.");
        }
    },

    /**
     *  Reads text from the file specified, and splits it into lines.
     *
     *  @returns Object with info on lines.
     */
    splitIntoLines: function(file_reader) {
        var arrSplit =  file_reader &&
                        file_reader.result &&
                        file_reader.result.split(/(\r\n|\r|\n)/);
        // The 'arrSplit' variable is an array where the line text and the
        // line delimiters are at separate indices.  Need to process that into
        // another array where the line delimiters are appended to the ends of
        // their associated lines.
        var arrLines = [];
        var strLine = "";
        for (var i = 0; i < arrSplit.length; i ++) {
            strLine += arrSplit[i];
            if (strLine.match(/\r\n|\r|\n/)) {
                arrLines.push(strLine);
                strLine = "";
            }
        }
        if (strLine) arrLines.push(strLine);

        function _getLine(indexLine) {
                return  arrLines &&
                        arrLines.length > indexLine &&
                        arrLines[indexLine] || "";
            }

        function _getLineStripped(indexLine) {
                var strLine = _getLine(indexLine);
                var arrBreakdownForLine = strLine &&
                                            strLine.match(/^\s*(.+\S+)\s*$/);
                return arrBreakdownForLine &&
                        arrBreakdownForLine.length == 2 &&
                        arrBreakdownForLine[1] || null;
            }

        var _arrLineTokens = [];

        function _insureTokenLineExists(indexLine) {
                if (_arrLineTokens[indexLine]) return;

                var strLineStripped = _getLineStripped(indexLine);
                _arrLineTokens[indexLine] = strLineStripped && strLineStripped.split(/\s/) || [];
            }

        return {
                /**
                 *  Total number of characters in the text file.
                 */
                totalChars: file_reader &&
                            file_reader.result &&
                            file_reader.result.length || 0,

                /**
                 *  Total number of lines in the text file.
                 *
                 *  The text file is stored line by line rather than in a
                 *  single string, as otherwise a huge file in a huge string
                 *  would overwhelm the browser's JavaScript engine.
                 */
                totalLines: arrLines && arrLines.length || 0,

                /**
                 *  Returns a single line out of the text file, including the
                 *  line termination carriage return character sequence.
                 *
                 *  @param  indexLine       Number  0-based index of the line.
                 */
                getLine: function(indexLine) {
                        return _getLine(indexLine);
                    },

                /**
                 *  Returns a single line out of the text file, stripped of
                 *  the whitespace bounding it.
                 *
                 *  @param  indexLine       Number  0-based index of the line.
                 */
                getLineStripped: function(indexLine) {
                        return _getLineStripped(indexLine);
                    },

                /**
                 *  Returns the total number of non-whitespace tokens on the
                 *  line with the index specified.
                 *
                 *  @param  indexLine       Number  0-based index of the line.
                 */
                getTotalTokensOnLine: function(indexLine) {
                        _insureTokenLineExists(indexLine);
                        return _arrLineTokens[indexLine].length;
                    },

                /**
                 *  Returns a single non-whitespace token with the index
                 *  specified on the line with the index specified.
                 *
                 *  @param  indexToken      Number  0-based index of the token
                                                                on the line.
                 *  @param  indexLine       Number  0-based index of the line.
                 *
                 *  Note that the token index is specified before the line
                 *  index.
                 */
                getTokenOnLine: function(indexToken, indexLine) {
                        _insureTokenLineExists(indexLine);
                        return _arrLineTokens[indexLine][indexToken];
                    }
            }
    }
}<|endoftext|>


<commit_before>/*jslint node: true, nomen: true, es5: true */
/**
 * Developed By Carlo Bernaschina (GitHub - B3rn475)
 * www.bernaschina.com
 *
 * Distributed under the MIT Licence
 */
"use strict";

var events = require('events'),
    path = require('path'),
    util = require('util');

function isIPRoute(route) {
    if (typeof route.port === undefined) {
        return false;
    }
    if (typeof route.port !== 'number') {
        return false;
    }
    return true;
}

function isLocalSocketRoute(route) {
    if (typeof route.path === undefined) {
        return false;
    }
    if (typeof route.path !== 'string') {
        return false;
    }
    return true;
}

function BasicRouter(options) {
    if (!(this instanceof BasicRouter)) {
        return new BasicRouter(options);
    }
    events.EventEmitter.call(this);

    options = options || {};

    var self = this,
        hosts = {}, // hosts lookup for removal and avoid duplicates
        paths = {}; // paths lookup for removal and avoid duplicates

    function addIPRoute(route) {
        var KO,
            internal,
            fixedhost;
        if (route.host !== undefined) {
            if (typeof route.host !== 'string') {
                KO = true;
            } else {
                if (route.localAddress !== undefined) {
                    if (typeof route.localAddress !== 'string') {
                        KO = true;
                    }
                }
            }
        }
        if (KO) {
            throw new Error('Error adding ip Route - Invalid Route');
        }
        fixedhost = route.host || 'localhost';
        if (hosts[fixedhost] === undefined) { // no routes known for the host
            hosts[fixedhost] = {};
        }
        if (hosts[fixedhost][route.port] === undefined) { // no routes known for port on the particular host
            internal = {
                host: route.host,
                port: route.port
            }; // avoid side effects and allow lookup
            if (internal.host !== undefined) {
                internal.localAddress = route.localAddress;
            }
            hosts[fixedhost][route.port] = internal; // register in the lookup
            self.emit('_routeAdded', internal); // emit private event
            self.emit('routeAdded', {
                port: internal.port,
                host: internal.host,
                localAddress: internal.localAddress
            }); // emit public event (avoid side effects)
        }
    }

    function addLocalSocketRoute(route) {
        var resolvedPath = path.resolve(router.path);
        if (paths[resolvedPath] === undefined) { // no routes known for the path
            var internal = {
                path: resolvedPath
            }; // avoid side effects and allow lookup
            paths[resolvedPath] = internal; // register in the lookup
            self.emit('_routeAdded', internal); // emit private event
            self.emit('routeAdded', {
                path: internal.path
            }); // emit public event (avoid side effects)
        }
    }

    this.addRoute = function (route) {
        if (typeof route === 'array') {
            route.forEach(this.addRoute, this);
        } else if (typeof route === 'object') {
            if (isIPRoute(route)) {
                addIPRoute(route);
            } else if (isLocalSocketRoute(route)) {
                addLocalSocketRoute(route);
            } else {
                throw new Error('Error adding Route - Invalid Route');
            }
        } else {
            throw new Error('Error adding Route - Route is not an Object');
        }
        return this;
    };

    function removeIPRoute(route) {
        var KO,
            internal,
            fixedhost;
        if (route.host !== undefined) {
            if (typeof route.host !== 'string') {
                KO = true;
            } else {
                if (route.localAddress !== undefined) {
                    if (typeof route.localAddress !== 'string') {
                        KO = true;
                    }
                }
            }
        }
        if (KO) {
            throw new Error('Error removing ip Route - Invalid Route');
        }
        fixedhost = route.host || 'localhost';
        if (hosts[fixedhost] !== undefined && hosts[fixedhost][route.port] !== undefined) { // the route is registered
            internal = hosts[fixedhost][route.port];
            hosts[fixedhost][route.port] = undefined;
            self.emit('_routeRemoved', internal); // emit private event
            self.emit('routeRemoved', {
                port: internal.port,
                host: internal.host,
                localAddress: internal.localAddress
            }); // emit public event (avoid side effects)
        }
        return self;
    }

    function removeLocalSocketRoute(route) {
        var resolvedPath = path.resolve(router.path);
        if (paths[resolvedPath] !== undefined) { // the route is registered
            var internal = paths[resolvedPath];
            paths[resolvedPath] = undefined;
            self.emit('_routeRemoved', internal); // emit private event
            self.emit('routeRemoved', {
                path: internal.path
            }); // emit public event (avoid side effects)
        }
        return self;
    }

    this.removeRoute = function (route) {
        if (typeof route === 'array') {
            route.forEach(this.removeRoute, this);
        } else if (typeof route === 'object') {
            if (isIPRoute(route)) {
                removeIPRoute(route);
            } else if (isLocalSocketRoute(route)) {
                removeLocalSocketRoute(route);
            } else {
                throw new Error('Error removing Route - Invalid Route');
            }
        } else {
            throw new Error('Error removing Route - Route is not an Object');
        }
        return this;
    };

    if (util.isArray(options.routes)) {
        options.routes.forEach(this.addRoute, this);
    }
}
util.inherits(BasicRouter, events.EventEmitter);

exports.Router = BasicRouter;
<commit_msg>Fixed typos<commit_after>/*jslint node: true, nomen: true, es5: true */
/**
 * Developed By Carlo Bernaschina (GitHub - B3rn475)
 * www.bernaschina.com
 *
 * Distributed under the MIT Licence
 */
"use strict";

var events = require('events'),
    path = require('path'),
    util = require('util');

function isIPRoute(route) {
    if (typeof route.port === undefined) {
        return false;
    }
    if (typeof route.port !== 'number') {
        return false;
    }
    return true;
}

function isLocalSocketRoute(route) {
    if (typeof route.path === undefined) {
        return false;
    }
    if (typeof route.path !== 'string') {
        return false;
    }
    return true;
}

function BasicRouter(options) {
    if (!(this instanceof BasicRouter)) {
        return new BasicRouter(options);
    }
    events.EventEmitter.call(this);

    options = options || {};

    var self = this,
        hosts = {}, // hosts lookup for removal and avoid duplicates
        paths = {}; // paths lookup for removal and avoid duplicates

    function addIPRoute(route) {
        var KO,
            internal,
            fixedhost;
        if (route.host !== undefined) {
            if (typeof route.host !== 'string') {
                KO = true;
            } else {
                if (route.localAddress !== undefined) {
                    if (typeof route.localAddress !== 'string') {
                        KO = true;
                    }
                }
            }
        }
        if (KO) {
            throw new Error('Error adding ip Route - Invalid Route');
        }
        fixedhost = route.host || 'localhost';
        if (hosts[fixedhost] === undefined) { // no routes known for the host
            hosts[fixedhost] = {};
        }
        if (hosts[fixedhost][route.port] === undefined) { // no routes known for port on the particular host
            internal = {
                host: route.host,
                port: route.port
            }; // avoid side effects and allow lookup
            if (internal.host !== undefined) {
                internal.localAddress = route.localAddress;
            }
            hosts[fixedhost][route.port] = internal; // register in the lookup
            self.emit('_routeAdded', internal); // emit private event
            self.emit('routeAdded', {
                port: internal.port,
                host: internal.host,
                localAddress: internal.localAddress
            }); // emit public event (avoid side effects)
        }
    }

    function addLocalSocketRoute(route) {
        var resolvedPath = path.resolve(route.path),
            internal;
        if (paths[resolvedPath] === undefined) { // no routes known for the path
            internal = {
                path: resolvedPath
            }; // avoid side effects and allow lookup
            paths[resolvedPath] = internal; // register in the lookup
            self.emit('_routeAdded', internal); // emit private event
            self.emit('routeAdded', {
                path: internal.path
            }); // emit public event (avoid side effects)
        }
    }

    this.addRoute = function (route) {
        if (typeof route === 'array') {
            route.forEach(this.addRoute, this);
        } else if (typeof route === 'object') {
            if (isIPRoute(route)) {
                addIPRoute(route);
            } else if (isLocalSocketRoute(route)) {
                addLocalSocketRoute(route);
            } else {
                throw new Error('Error adding Route - Invalid Route');
            }
        } else {
            throw new Error('Error adding Route - Route is not an Object');
        }
        return this;
    };

    function removeIPRoute(route) {
        var KO,
            internal,
            fixedhost;
        if (route.host !== undefined) {
            if (typeof route.host !== 'string') {
                KO = true;
            } else {
                if (route.localAddress !== undefined) {
                    if (typeof route.localAddress !== 'string') {
                        KO = true;
                    }
                }
            }
        }
        if (KO) {
            throw new Error('Error removing ip Route - Invalid Route');
        }
        fixedhost = route.host || 'localhost';
        if (hosts[fixedhost] !== undefined && hosts[fixedhost][route.port] !== undefined) { // the route is registered
            internal = hosts[fixedhost][route.port];
            hosts[fixedhost][route.port] = undefined;
            self.emit('_routeRemoved', internal); // emit private event
            self.emit('routeRemoved', {
                port: internal.port,
                host: internal.host,
                localAddress: internal.localAddress
            }); // emit public event (avoid side effects)
        }
        return self;
    }

    function removeLocalSocketRoute(route) {
        var resolvedPath = path.resolve(route.path),
            internal;
        if (paths[resolvedPath] !== undefined) { // the route is registered
            internal = paths[resolvedPath];
            paths[resolvedPath] = undefined;
            self.emit('_routeRemoved', internal); // emit private event
            self.emit('routeRemoved', {
                path: internal.path
            }); // emit public event (avoid side effects)
        }
        return self;
    }

    this.removeRoute = function (route) {
        if (typeof route === 'array') {
            route.forEach(this.removeRoute, this);
        } else if (typeof route === 'object') {
            if (isIPRoute(route)) {
                removeIPRoute(route);
            } else if (isLocalSocketRoute(route)) {
                removeLocalSocketRoute(route);
            } else {
                throw new Error('Error removing Route - Invalid Route');
            }
        } else {
            throw new Error('Error removing Route - Route is not an Object');
        }
        return this;
    };

    if (util.isArray(options.routes)) {
        options.routes.forEach(this.addRoute, this);
    }
}
util.inherits(BasicRouter, events.EventEmitter);

exports.Router = BasicRouter;
<|endoftext|>