<commit_before>var fs = require('fs');
//var S = require('string');

var sections;
var sectionOrder;
var currentSection;
var lyricLine;
var orderSet;
var sectionNum;

function toTitleCase(str)
{
    return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
}

function getSection(section)
{
	return sections[section];
}

function getithSectionName(i)
{
	return sectionOrder[i];
}

function getithSection(i)
{
	return sections[sectionOrder[i]];
}

// function printDoc()
// {
// console.log(getSection(''));
// for( var i = 1; i <=sectionNum;i++) //for testing
// 			console.log('
' + getithSectionName(i).toUpperCase() + ':
' + '\n' + (getithSection(i) || '')); //remove later
// }

// Return result as html page string
// function printDoc(font,csspath)
function printDoc(font)
{

	// var result = "<!DOCTYPE html>\n<link rel=\"stylesheet\" type=\"text/css\" href=\"" + csspath + "\">\n\n";
	//console.log(getSection(''));
	var result = "";
	if(font)
		result+= '';

	result += "
" + getSection('@')+"
\n";
	for( var i = 1; i <=sectionNum;i++) { //for testing
		var s =  getithSection(i);
		if(!s) continue;
		s.replace(/\n+$/,"")
		var sn = getithSectionName(i);
		result += "
"+toTitleCase(sn)+ "\n" + s + "
\n"; //class=\"" + sn + "\"
	}
	// result += "\n";
	return result;
}

function setOrder(orderLine)
{
	var sectArray = orderLine.substring(6).split(",");
	var sect = '';
	for(var i = 0; i < sectArray.length; i++){
		sectionNum++;
		sect = sectArray[i].trim().toLowerCase();
		sectionOrder[sectionNum] = sect;
	}
	orderSet = true;
}
function pushToSection(sect,lines)
{
	while((lines+= -1) > 0)
		sections[sect] += "
";
	sections[sect] += lyricLine + "\n" ;
	lyricLine = '';
}

//NEEDS TO BE HTML ESCAPE SANITIZED
function checkBracketErrors(oneLine, linenum){
//check for basic bracket errors
	var state = '';
	var len = oneLine.length;

	for (var i = 0; i < len; i++) {
		switch(state){
		case '':
			switch(oneLine[i]){
			case '{': case '[': case '<':
				state = oneLine[i]; break;
			case '}': case ']':
				return 'There is an extra \'' + oneLine[i] + '\' on line ' + linenum + '.';
			case '>':
				return 'There is an extra \'<\' on line ' + linenum + '.';
			default:
			}
			break;
		case '[':
			switch(oneLine[i]){
			case ']':
				state = ''; break;
			case '{': case '[': case '}':
				return 'There is an unexpected \'' + oneLine[i] +  '\' before an expected \']\' in line ' + linenum +  '.';
			case '<':
				return 'There is an unexpected \'<\' before an expected \']\' in line ' + linenum +  '.';
			case '>':
				return 'There is an unexpected \'>\' before an expected \']\' in line ' + linenum +  '.';
			default:
			}
			break;
		case '{':
			switch(oneLine[i]){
			case '}':
				state = ''; break;
			case '{': case '[': case ']':
				return 'There is an unexpected \'' + oneLine[i] +  '\' before an expected \'}\' in line ' + linenum +  '.';
			case '<':
				return 'There is an unexpected \'<\' before an expected \']\' in line ' + linenum +  '.';
			case '>':
				return 'There is an unexpected \'>\' before an expected \']\' in line ' + linenum +  '.';
			default:
			}
			break;
		case '<':
			switch(oneLine[i]){
			case '>':
				state = ''; break;
			case '{': case '[':
			case ']':case '}':
				return 'There is an unexpected \'' + oneLine[i] +  '\' before an expected \'>\' in line ' + linenum +  '.'
			case '<':
				return 'There is an unexpected \'<\' before an expected \']\' in line ' + linenum +  '.';
			default:
			}
			break;
		}
	}
	if(state !== '') return 'A \'' + state + '\' bracket on line ' + linenum + ' is not closed.';
}
//handle chord in []. i is position of [ on oneLine
function parseChord(oneLine, i){
	j = i;
	var newChord = '';
	while(oneLine[++j] !== ']')
		newChord += oneLine[j];
		lyricLine += ""
	return j-i;

}
//handle chord comment in <>. i is position of < on oneLine
function parseChordComment(oneLine, i){
		j = i;
	var newChordComment = '';
	while(oneLine[++j] !== '>')
		newChordComment += oneLine[j];
		lyricLine += ""
	return j-i;
}

//handle options in {}. i is position of [ on oneLine
function parseOption(oneLine, i){
	var option = '';
	j = i;
	while(oneLine[++j] !== '}')
		option += oneLine[j];
	option = option.trim().toLowerCase()
	switch(option)
	{
		case 'bold': case 'start bold': case 'startbold':
			lyricLine += "";
			return j-i;
		case 'endbold':	case 'end bold':
			lyricLine += "";
			return j-i;
		case 'italic':case 'ital': case 'start italic': case 'start ital': case 'startitalic': case 'startital':
			lyricLine += "";
			return j-i;
		case 'enditalic': case 'end italic': case 'endital': case 'end ital':
			lyricLine += "";
			return j-i;
		case 'tab': case 'start tab': case 'starttab':
			lyricLine += "";
			return j-i;
		case 'end tab': case 'endtab':
			lyricLine += "";
			return j-i;
		case 'heading': case 'start heading': case 'startheading':
			lyricLine += "";
			return j-i;
		case 'end heading': case 'endheading':
			lyricLine += "";
			return j-i;
		default:

	}

	//option is a section
	if(lyricLine !== '')
		pushToSection(currentSection);
	currentSection = option;
	if(!sections[currentSection]){
		sections[currentSection] = '';
		if(!orderSet)
			sectionOrder[++sectionNum] = currentSection;
		}
	else
		sections[currentSection] += 'WARNING: Multiple defitions of section ' + currentSection + '. Behavior undefined.\n';

	return j-i;
}

/* assuming [chord]lyric format */
// do something with font variable later
function parseLine(oneLine, linenum, font) {
	oneLine = oneLine.replace(/\r/g,"");
	var lines = 0;
	if(linenum === 1 && /^order:/i.test(oneLine)){
		setOrder(oneLine);
		return;
	}

	var bErr = checkBracketErrors(oneLine, linenum);
	if(bErr) {
		lyricLine = '' + bErr + '';
		pushToSection(currentSection,1);
		return;
	}
	//Presume no bracketing errors from here on out.

	if(/^\s*\{.*\}\s*$/.test(oneLine)){ //just single option
		parseOption(oneLine.trim(), 0);
		return;
	}

	/*if(!/[\[{<]/.test(oneLine)) { //if bracketless, simply print the line
		lyricLine = oneLine;
		pushToSection(currentSection);
		return;
	}*/
	//if just spaces/newlines,
	if(/^\s*$/.test(oneLine)){
		lyricLine += oneLine;
		return;
	}
	//Otherwise, read in character by character, usually putting in the lyric line, and calling handlers for brackets.
	var len = oneLine.length;
	for (var i = 0; i < len; i++) {
		switch(oneLine[i]){
		case '[':
			lines = 2;
			i+=parseChord(oneLine, i);
			break;
		case '<':
			lines = 2;
			i+=parseChordComment(oneLine, i);
			break;
		case '{':
			i+=parseOption(oneLine, i);
			break;
		case '/':
		// '//' rest of line unprinted comment?
			if(oneLine[i+1] ==='/'){
				i = len;
				break;
			}
		default:
			lyricLine+= oneLine[i];
		}

	}
	pushToSection(currentSection, lines);

}

// function readLines(input, font) {
// function readLines(input, callback, font, csspath) {
function readLines(input, font, callback) {
	//initialize global variables
	sections = {'@':''};
	sectionOrder = {0:'@'}
	currentSection = '@';
	lyricLine = '';
	orderSet = false;
	sectionNum = 0;

	var lines = input.split('\n');

	for(i = 0; i < lines.length; i++)
		parseLine(lines[i], i + 1);
	//console.log(printDoc(font, csspath));
	// callback(printDoc(font, csspath));
	callback(printDoc(font));

}
exports.parseSongHTML = readLines;

// Integrate parser --> THIS FUNCTION NEEDS TO BE FIXED
// TAKE IN STRING INSTEAD OF WRITING TEMP FILE
/*exports.parseSong = function(data, callback) {
	fs.writeFile('./tmp.txt', data, function(err) {
		if(err) {
			console.log(err);
			return 'error';
		} else {
			console.log('success!');
			var input = fs.createReadStream('tmp.txt');
			var result = readLines(input, function(result) {
				fs.unlink('./tmp.txt', function (err) {
					if (err) {
						console.log(err);
						return 'error';
					} else {
						console.log('success delete file');
						console.log('In parser: ' + result);
						callback(result);
					}
				});
			});
		}
	});
}
*/
/*
var input = fs.createReadStream('lines.txt');
var remaining = ''
input.on('data', function(data) {
		remaining += data;
	})
input.on('end', function() {
	readLines(remaining,"blah","Georgia","songsheetformat.css");

	})
*/<commit_msg>font sizes<commit_after>var fs = require('fs');
//var S = require('string');

var sections;
var sectionOrder;
var currentSection;
var lyricLine;
var orderSet;
var sectionNum;

function toTitleCase(str)
{
    return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
}

function getSection(section)
{
	return sections[section];
}

function getithSectionName(i)
{
	return sectionOrder[i];
}

function getithSection(i)
{
	return sections[sectionOrder[i]];
}

// function printDoc()
// {
// console.log(getSection(''));
// for( var i = 1; i <=sectionNum;i++) //for testing
// 			console.log('
' + getithSectionName(i).toUpperCase() + ':
' + '\n' + (getithSection(i) || '')); //remove later
// }

// Return result as html page string
// function printDoc(font,csspath)
function printDoc(font,fontsize)
{

	// var result = "<!DOCTYPE html>\n<link rel=\"stylesheet\" type=\"text/css\" href=\"" + csspath + "\">\n\n";
	//console.log(getSection(''));
	var result = "";
	if(font)
		result+= '';

	result += "
" + getSection('@')+"
\n";
	for( var i = 1; i <=sectionNum;i++) { //for testing
		var s =  getithSection(i);
		if(!s) continue;
		s.replace(/\n+$/,"")
		var sn = getithSectionName(i);
		result += "
"+toTitleCase(sn)+ "\n" + s + "
\n"; //class=\"" + sn + "\"
	}
	// result += "\n";
	return result;
}

function setOrder(orderLine)
{
	var sectArray = orderLine.substring(6).split(",");
	var sect = '';
	for(var i = 0; i < sectArray.length; i++){
		sectionNum++;
		sect = sectArray[i].trim().toLowerCase();
		sectionOrder[sectionNum] = sect;
	}
	orderSet = true;
}
function pushToSection(sect,lines)
{
	while((lines+= -1) > 0)
		sections[sect] += "
";
	sections[sect] += lyricLine + "\n" ;
	lyricLine = '';
}

//NEEDS TO BE HTML ESCAPE SANITIZED
function checkBracketErrors(oneLine, linenum){
//check for basic bracket errors
	var state = '';
	var len = oneLine.length;

	for (var i = 0; i < len; i++) {
		switch(state){
		case '':
			switch(oneLine[i]){
			case '{': case '[': case '<':
				state = oneLine[i]; break;
			case '}': case ']':
				return 'There is an extra \'' + oneLine[i] + '\' on line ' + linenum + '.';
			case '>':
				return 'There is an extra \'<\' on line ' + linenum + '.';
			default:
			}
			break;
		case '[':
			switch(oneLine[i]){
			case ']':
				state = ''; break;
			case '{': case '[': case '}':
				return 'There is an unexpected \'' + oneLine[i] +  '\' before an expected \']\' in line ' + linenum +  '.';
			case '<':
				return 'There is an unexpected \'<\' before an expected \']\' in line ' + linenum +  '.';
			case '>':
				return 'There is an unexpected \'>\' before an expected \']\' in line ' + linenum +  '.';
			default:
			}
			break;
		case '{':
			switch(oneLine[i]){
			case '}':
				state = ''; break;
			case '{': case '[': case ']':
				return 'There is an unexpected \'' + oneLine[i] +  '\' before an expected \'}\' in line ' + linenum +  '.';
			case '<':
				return 'There is an unexpected \'<\' before an expected \']\' in line ' + linenum +  '.';
			case '>':
				return 'There is an unexpected \'>\' before an expected \']\' in line ' + linenum +  '.';
			default:
			}
			break;
		case '<':
			switch(oneLine[i]){
			case '>':
				state = ''; break;
			case '{': case '[':
			case ']':case '}':
				return 'There is an unexpected \'' + oneLine[i] +  '\' before an expected \'>\' in line ' + linenum +  '.'
			case '<':
				return 'There is an unexpected \'<\' before an expected \']\' in line ' + linenum +  '.';
			default:
			}
			break;
		}
	}
	if(state !== '') return 'A \'' + state + '\' bracket on line ' + linenum + ' is not closed.';
}
//handle chord in []. i is position of [ on oneLine
function parseChord(oneLine, i){
	j = i;
	var newChord = '';
	while(oneLine[++j] !== ']')
		newChord += oneLine[j];
		lyricLine += ""
	return j-i;

}
//handle chord comment in <>. i is position of < on oneLine
function parseChordComment(oneLine, i){
		j = i;
	var newChordComment = '';
	while(oneLine[++j] !== '>')
		newChordComment += oneLine[j];
		lyricLine += ""
	return j-i;
}

//handle options in {}. i is position of [ on oneLine
function parseOption(oneLine, i){
	var option = '';
	j = i;
	while(oneLine[++j] !== '}')
		option += oneLine[j];
	option = option.trim().toLowerCase()
	switch(option)
	{
		case 'bold': case 'start bold': case 'startbold':
			lyricLine += "";
			return j-i;
		case 'endbold':	case 'end bold':
			lyricLine += "";
			return j-i;
		case 'italic':case 'ital': case 'start italic': case 'start ital': case 'startitalic': case 'startital':
			lyricLine += "";
			return j-i;
		case 'enditalic': case 'end italic': case 'endital': case 'end ital':
			lyricLine += "";
			return j-i;
		case 'tab': case 'start tab': case 'starttab':
			lyricLine += "";
			return j-i;
		case 'end tab': case 'endtab':
			lyricLine += "";
			return j-i;
		case 'heading': case 'start heading': case 'startheading':
			lyricLine += "";
			return j-i;
		case 'end heading': case 'endheading':
			lyricLine += "";
			return j-i;
		default:

	}

	//option is a section
	if(lyricLine !== '')
		pushToSection(currentSection);
	currentSection = option;
	if(!sections[currentSection]){
		sections[currentSection] = '';
		if(!orderSet)
			sectionOrder[++sectionNum] = currentSection;
		}
	else
		sections[currentSection] += 'WARNING: Multiple defitions of section ' + currentSection + '. Behavior undefined.\n';

	return j-i;
}

/* assuming [chord]lyric format */
// do something with font variable later
function parseLine(oneLine, linenum, font) {
	oneLine = oneLine.replace(/\r/g,"");
	var lines = 0;
	if(linenum === 1 && /^order:/i.test(oneLine)){
		setOrder(oneLine);
		return;
	}

	var bErr = checkBracketErrors(oneLine, linenum);
	if(bErr) {
		lyricLine = '' + bErr + '';
		pushToSection(currentSection,1);
		return;
	}
	//Presume no bracketing errors from here on out.

	if(/^\s*\{.*\}\s*$/.test(oneLine)){ //just single option
		parseOption(oneLine.trim(), 0);
		return;
	}

	/*if(!/[\[{<]/.test(oneLine)) { //if bracketless, simply print the line
		lyricLine = oneLine;
		pushToSection(currentSection);
		return;
	}*/
	//if just spaces/newlines,
	if(/^\s*$/.test(oneLine)){
		lyricLine += oneLine;
		return;
	}
	//Otherwise, read in character by character, usually putting in the lyric line, and calling handlers for brackets.
	var len = oneLine.length;
	for (var i = 0; i < len; i++) {
		switch(oneLine[i]){
		case '[':
			lines = 2;
			i+=parseChord(oneLine, i);
			break;
		case '<':
			lines = 2;
			i+=parseChordComment(oneLine, i);
			break;
		case '{':
			i+=parseOption(oneLine, i);
			break;
		case '/':
		// '//' rest of line unprinted comment?
			if(oneLine[i+1] ==='/'){
				i = len;
				break;
			}
		default:
			lyricLine+= oneLine[i];
		}

	}
	pushToSection(currentSection, lines);

}

// function readLines(input, font) {
// function readLines(input, callback, font, csspath) {
function readLines(input, font, callback, fontsize) {
	//initialize global variables
	sections = {'@':''};
	sectionOrder = {0:'@'}
	currentSection = '@';
	lyricLine = '';
	orderSet = false;
	sectionNum = 0;

	var lines = input.split('\n');

	for(i = 0; i < lines.length; i++)
		parseLine(lines[i], i + 1);
	//console.log(printDoc(font, csspath));
	// callback(printDoc(font, csspath));
	callback(printDoc(font,fontsize));

}
exports.parseSongHTML = readLines;

// Integrate parser --> THIS FUNCTION NEEDS TO BE FIXED
// TAKE IN STRING INSTEAD OF WRITING TEMP FILE
/*exports.parseSong = function(data, callback) {
	fs.writeFile('./tmp.txt', data, function(err) {
		if(err) {
			console.log(err);
			return 'error';
		} else {
			console.log('success!');
			var input = fs.createReadStream('tmp.txt');
			var result = readLines(input, function(result) {
				fs.unlink('./tmp.txt', function (err) {
					if (err) {
						console.log(err);
						return 'error';
					} else {
						console.log('success delete file');
						console.log('In parser: ' + result);
						callback(result);
					}
				});
			});
		}
	});
}
*/
/*
var input = fs.createReadStream('lines.txt');
var remaining = ''
input.on('data', function(data) {
		remaining += data;
	})
input.on('end', function() {
	readLines(remaining,"blah","Georgia","songsheetformat.css");

	})
*/<|endoftext|>


<commit_before>/*
 * CometJS / simple.js
 * copyright (c) 2014 Susisu
 */

"use strict";

function end () {
    module.exports = Object.freeze({
        "Simple"        : Simple,
        "ThreadOption"  : ThreadOption,
        "NoteThread"    : NoteThread,
        "LongNoteThread": LongNoteThread
    });
}

var comet = {
    "input" : require("./input.js"),
    "score" : require("./score.js"),
    "thread": require("./thread.js"),
    "util"  : require("./util.js")
};

function Simple(sources) {
    this.sources = sources;
}

Object.defineProperties(Simple.prototype, {
    "makeThreads": {
        "value": function (options) {
            var sources = this.sources.map(function (source) { return source.clone(); });
            if (this.options.length < sources.length) {
                throw new Error("");
            }
            var threads = [];
            for (var i = 0; i < sources.length; i++) {
                var source = sources[i];
                var option = options[i];
                var noteThread = new NoteThread(
                    source.noteSource,
                    option.inputDevice,
                    option.hitRange,
                    option.speedMultiplier,
                    option.noteEntityBuilder
                );
                var longNoteThread = new LongNoteThread(
                    source.longNoteSource,
                    option.inputDevice,
                    option.hitRange,
                    option.speedMultiplier,
                    option.longNoteEntityBuilder
                );
                threads.push(noteThread, longNoteThread);
            }
            return threads;
        }
    }
});

function ThreadOption(inputDevice, hitRange, stdDuration, speedMultiplier, noteEntityBuilder, longNoteEntityBuilder) {
    this.inputDevice           = inputDevice;
    this.hitRange              = hitRange;
    this.stdDuration           = stdDuration;
    this.speedMultiplier       = speedMultiplier;
    this.noteEntityBuilder     = noteEntityBuilder;
    this.longNoteEntityBuilder = longNoteEntityBuilder;
}

function NoteThread(source, inputDevice, hitRange, stdDuration, speedMultiplier, entityBuilder) {
    this._source          = source;
    this._inputDevice     = inputDevice;
    this._hitRange        = hitRange;
    this._stdDuration     = stdDuration;
    this._speedMultiplier = speedMultiplier;
    this._entityBuilder   = entityBuilder;

    this._entities         = null;
    this._noteIndex        = 0;
    this._speed            = 1.0;
    this._speedChangeIndex = 0;
}

NoteThread.prototype = Object.create(comet.thread.IThread.prototype, {
    "constructor": {
        "value"       : NoteThread,
        "writable"    : true,
        "configurable": true
    }
});

Object.defineProperties(NoteThread.prototype, {
    "init": {
        "value": function (initialFrame) {
            this._source.init(this._stdDuration, this._speedMultiplier);
            this._entities         = [];
            this._noteIndex        = 0;
            this.speed             = 1.0;
            this._speedChangeIndex = 0;

            var currentFrame = Math.min(
                this._source.notes.length        > 0 ? this._source.notes[0].appearFrame  : initialFrame,
                this._source.speedChanges.length > 0 ? this._source.speedChanges[0].frame : initialFrame
            );
            while (currentFrame <= initialFrame) {
                this._initTick(currentFrame);
                currentFrame++;
            }
        }
    },
    "tick": {
        "value": function (currentFrame) {
            this._moveEntities(true);
            this._createEntities(currentFrame);
            this._changeSpeed(currentFrame);
            this._changeProperties(currentFrame);
        }
    },
    "_initTick": {
        "value": function (currentFrame) {
            this._moveEntities(false);
            this._createEntities(currentFrame);
            this._changeSpeed(currentFrame);
            this._changeProperties(currentFrame);
        }
    },
    "_moveEntities": {
        "value": function (dispatchEvent) {

        }
    },
    "_createEntities": {
        "value": function (currentFrame) {
            var numNotes = this._source.notes.length;
            while (this._noteIndex < numNotes && this._source.notes[this._noteIndex].appearFrame == currentFrame) {
                if (this._entityBuilder) {
                    var note   = this._source.notes[this._noteIndex];
                    var entity = this._entityBuilder.createNoteEntity(
                        -1.0 + note.posCorrection,
                        note.relSpeed,
                        note.appearFrame - note.hitFrame,
                        cloneObject(note.properties)
                    );
                    this._insertEntity(entity);
                }
                this._noteIndex++;
            }
        }
    },
    "_insertEntity": {
        "value": function (entity) {
            var numEntities = this._entities.length;
            for (var i = 0; i < numEntities; i++) {
                if (entity.frame > this._entities[i].frame) {
                    this._entities.splice(i, 0, entity);
                    return;
                }
            }
            this._entities.push(entity);
        }
    },
    "_changeSpeed": {
        "value": function (currentFrame) {

        }
    },
    "_changeProperties": {
        "value": function (currentFrame) {

        }
    }
});

function LongNoteThread(source, inputDevice, hitRange, stdDuration, speedMultiplier, entityBuilder) {
    this._source          = source;
    this._inputDevice     = inputDevice;
    this._hitRange        = hitRange;
    this._stdDuration     = stdDuration;
    this._speedMultiplier = speedMultiplier;
    this._entityBuilder   = entityBuilder;
}

LongNoteThread.prototype = Object.create(comet.thread.IThread.prototype, {
    "constructor": {
        "value"       : LongNoteThread,
        "writable"    : true,
        "configurable": true
    }
});

Object.defineProperties(LongNoteThread.prototype, {
    "init": {
        "value": function (initialFrame) {

        }
    },
    "tick": {
        "value": function (currentFrame) {

        }
    }
});

function Source(noteSource, longNoteSource) {
    this.noteSource     = noteSource;
    this.longNoteSource = longNoteSource;
}

Object.defineProperties(Source.prototype, {
    "clone": {
        "value": function () {
            return new Source(this.noteSource.clone(), this.longNoteSource.clone());
        }
    },
    "init": {
        "value": function (stdDuration, speedMultiplier) {
            this.noteSource.init(stdDuration, speedMultiplier);
            this.longNoteSource.init(stdDuration, speedMultiplier);
        }
    }
});

function NoteSource(notes, speedChanges, propertyChanges) {
    this.notes           = notes;
    this.speedChanges    = speedChanges;
    this.propertyChanges = propertyChanges;
}

Object.defineProperties(NoteSource.prototype, {
    "clone": {
        "value": function () {
            return new LongNoteSource(
                this.notes.map(function (note) { return note.clone(); }),
                this.speedChanges.map(function (speedChange) { return speedChange.clone(); }),
                this.propertyChanges.map(function (propertyChange) { return propertyChange.clone(); })

            );
        }
    },
    "init": {
        "value": function (stdDuration, speedMultiplier) {
            this.speedChanges    = comet.util.stableSort(this.speedChanges, byFrames);
            this.propertyChanges = comet.util.stableSort(this.propertyChanges, byFrames);

            this.notes = comet.util.stableSort(this.notes, byHitFrames);

            var numSpeedChanges = this.speedChanges.length;
            var numNotes        = this.notes.length;

            var noteIndex;
            var speedChangeIndex = 0;

            for (noteIndex = 0; noteIndex < numNotes; noteIndex++) {
                var note = this.notes[noteIndex];

                while (speedChangeIndex < 0 || (speedChangeIndex < numSpeedChanges
                    && this.speedChanges[speedChangeIndex].frame < note.hitFrame)) {
                    speedChangeIndex++;
                }
                speedChangeIndex--;

                var currentSpeed    = speedChangeIndex < 0 ? 1.0 : this.speedChanges[speedChangeIndex].speed;
                var currentFrame    = note.hitFrame;
                var currentPosition = 0.0;

                while (currentPosition > -1.0) {
                    currentFrame--;

                    while (speedChangeIndex >= 0 && currentFrame < this.speedChanges[speedChangeIndex].frame) {
                        speedChangeIndex--;

                        if (speedChangeIndex < 0) {
                            currentSpeed = 1.0
                        }
                        else {
                            currentSpeed = this.speedChanges[speedChangeIndex].speed;
                        }
                    }

                    currentPosition -= currentSpeed * note.relSpeed * speedMultiplier / stdDuration;
                }

                note.appearFrame   = currentFrame;
                note.posCorrection = currentPos + 1.0;
            }

            this.notes = comet.util.stableSort(this.notes, byAppearFrames);
        }
    }
});

function LongNoteSource(longNotes, speedChanges, propertyChanges) {
    this.longNotes       = longNotes;
    this.speedChanges    = speedChanges;
    this.propertyChanges = propertyChanges;
}

Object.defineProperties(LongNoteSource.prototype, {
    "clone": {
        "value": function () {
            return new LongNoteSource(
                this.longNotes.map(function (longNote) { return longNote.clone(); }),
                this.speedChanges.map(function (speedChange) { return speedChange.clone(); }),
                this.propertyChanges.map(function (propertyChange) { return propertyChange.clone(); })

            );
        }
    },
    "init": {
        "value": function (stdDuration, speedMultiplier) {
            this.speedChanges    = comet.util.stableSort(this.speedChanges, byFrames);
            this.propertyChanges = comet.util.stableSort(this.propertyChanges, byFrames);

            this.longNotes = comet.util.stableSort(this.longNotes, byHitFrames);

            var numSpeedChanges = this.speedChanges.length;
            var numLongNotes    = this.longNotes.length;

            var longNoteIndex;
            var speedChangeIndex = 0;

            for (longNoteIndex = 0; longNoteIndex < numLongNotes; longNoteIndex++) {
                var longNote = this.longNotes[longNoteIndex];

                while (speedChangeIndex < 0 || (speedChangeIndex < numSpeedChanges
                    && this.speedChanges[speedChangeIndex].frame < longNote.endFrame)) {
                    speedChangeIndex++;
                }
                speedChangeIndex--;

                var currentSpeed    = speedChangeIndex < 0 ? 1.0 : this.speedChanges[speedChangeIndex].speed;
                var currentFrame    = longNote.endFrame;
                var currentPosition = 0.0;
                var currentLength   = 0.0;

                while (currentPosition > -1.0) {
                    currentFrame--;

                    while (speedChangeIndex >= 0 && currentFrame < this.speedChanges[speedChangeIndex].frame) {
                        speedChangeIndex--;

                        if (speedChangeIndex < 0) {
                            currentSpeed = 1.0;
                        }
                        else {
                            currentSpeed = this.speedChanges[speedChangeIndex].speed;
                        }
                    }

                    if (longNote.hitFrame <= currentFrame) {
                        currentLength += currentSpeed * longNote.relSpeed * speedMultiplier / stdDuration;
                    }
                    else {
                        currentPosition -= currentSpeed * longNote.relSpeed * speedMultiplier / stdDuration;
                    }
                }

                longNote.appearFrame   = currentFrame;
                longNote.length        = currentLength;
                longNote.posCorrection = currentPosition + 1.0;
            }

            this.longNotes = comet.util.stableSort(this.longNotes, byAppearFrames);
        }
    }
});

function Note(hitFrame, relSpeed, properties) {
    this.hitFrame      = hitFrame;
    this.relSpeed      = relSpeed;
    this.appearFrame   = 0;
    this.posCorrection = 0;
    this.properties    = cloneObject(properties);
}

Object.defineProperties(Note.prototype, {
    "clone": {
        "value": function () {
            var note = new Note(this.hitFrame, this.relSpeed, cloneObject(this.properties));
            note.appearFrame   = this.appearFrame;
            note.posCorrection = this.posCorrection;
            return note;
        }
    }
});

function LongNote(hitFrame, endFrame, relSpeed, properties) {
    this.hitFrame      = hitFrame;
    this.endFrame      = endFrame;
    this.relSpeed      = relSpeed;
    this.appearFrame   = 0;
    this.length        = 0;
    this.posCorrection = 0;
    this.properties    = cloneObject(properties);
}

Object.defineProperties(LongNote.prototype, {
    "clone": {
        "value": function () {
            var longNote = new LongNote(this.hitFrame, this.endFrame, this.relSpeed, cloneObject(this.properties));
            longNote.appearFrame   = this.appearFrame;
            longNote.length        = this.length;
            longNote.posCorrection = this.posCorrection;
            return longNote;
        }
    }
});

function SpeedChange(frame, speed) {
    this.frame = frame;
    this.speed = speed;
}

Object.defineProperties(SpeedChange.prototype, {
    "clone": {
        "value": function () {
            return new SpeedChange(this.frame, this.speed);
        }
    }
});

function PropertyChange(frame, name, value) {
    this.frame = frame;
    this.name  = name;
    this.value = value;
}

Object.defineProperties(PropertyChange.prototype, {
    "clone": {
        "value": function () {
            return new PropertyChange(this.frame, this.name, this.value);
        }
    }
});

function byFrames(x, y) {
    return x.frame - y.frame;
}

function byHitFrames(x, y) {
    return x.hitFrame - y.hitFrame;
}

function byAppearFrames(x, y) {
    return x.appearFrame - y.appearFrame;
}

function cloneObject(object) {
    if (typeof object !== "object" || object === null) {
        return object;
    }
    else {
        var copy = {};
        for (var key in object) {
            if (object.hasOwnProperty(key)) {
                copy[key] = object[key];
            }
        }
        return copy;
    }
}

end();
<commit_msg>Update tick process<commit_after>/*
 * CometJS / simple.js
 * copyright (c) 2014 Susisu
 */

"use strict";

function end () {
    module.exports = Object.freeze({
        "Simple"        : Simple,
        "ThreadOption"  : ThreadOption,
        "NoteThread"    : NoteThread,
        "LongNoteThread": LongNoteThread
    });
}

var comet = {
    "input" : require("./input.js"),
    "score" : require("./score.js"),
    "thread": require("./thread.js"),
    "util"  : require("./util.js")
};

function Simple(sources) {
    this.sources = sources;
}

Object.defineProperties(Simple.prototype, {
    "makeThreads": {
        "value": function (options) {
            var sources = this.sources.map(function (source) { return source.clone(); });
            if (this.options.length < sources.length) {
                throw new Error("");
            }
            var threads = [];
            for (var i = 0; i < sources.length; i++) {
                var source = sources[i];
                var option = options[i];
                var noteThread = new NoteThread(
                    source.noteSource,
                    option.inputDevice,
                    option.hitRange,
                    option.speedMultiplier,
                    option.noteEntityBuilder
                );
                var longNoteThread = new LongNoteThread(
                    source.longNoteSource,
                    option.inputDevice,
                    option.hitRange,
                    option.speedMultiplier,
                    option.longNoteEntityBuilder
                );
                threads.push(noteThread, longNoteThread);
            }
            return threads;
        }
    }
});

function ThreadOption(inputDevice, hitRange, stdDuration, speedMultiplier, noteEntityBuilder, longNoteEntityBuilder) {
    this.inputDevice           = inputDevice;
    this.hitRange              = hitRange;
    this.stdDuration           = stdDuration;
    this.speedMultiplier       = speedMultiplier;
    this.noteEntityBuilder     = noteEntityBuilder;
    this.longNoteEntityBuilder = longNoteEntityBuilder;
}

function NoteThread(source, inputDevice, hitRange, stdDuration, speedMultiplier, entityBuilder) {
    this._source          = source;
    this._inputDevice     = inputDevice;
    this._hitRange        = hitRange;
    this._stdDuration     = stdDuration;
    this._speedMultiplier = speedMultiplier;
    this._entityBuilder   = entityBuilder;

    this._entities            = null;
    this._noteIndex           = 0;
    this._speed               = 1.0;
    this._speedChangeIndex    = 0;
    this._propertyChangeIndex = 0;
}

NoteThread.prototype = Object.create(comet.thread.IThread.prototype, {
    "constructor": {
        "value"       : NoteThread,
        "writable"    : true,
        "configurable": true
    }
});

Object.defineProperties(NoteThread.prototype, {
    "init": {
        "value": function (initialFrame) {
            this._source.init(this._stdDuration, this._speedMultiplier);
            this._entities            = [];
            this._noteIndex           = 0;
            this._speed               = 1.0;
            this._speedChangeIndex    = 0;
            this._propertyChangeIndex = 0;

            var currentFrame = Math.min(
                this._source.notes.length        > 0 ? this._source.notes[0].appearFrame  : initialFrame,
                this._source.speedChanges.length > 0 ? this._source.speedChanges[0].frame : initialFrame
            );
            while (currentFrame <= initialFrame) {
                this._initTick(currentFrame);
                currentFrame++;
            }
        }
    },
    "tick": {
        "value": function (currentFrame) {
            this._moveEntities(true);
            this._createEntities(currentFrame);
            this._changeSpeed(currentFrame);
            this._changeProperties(currentFrame);
        }
    },
    "_initTick": {
        "value": function (currentFrame) {
            this._moveEntities(false);
            this._createEntities(currentFrame);
            this._changeSpeed(currentFrame);
            this._changeProperties(currentFrame);
        }
    },
    "_moveEntities": {
        "value": function (dispatchEvent) {
            var numEntities = this._entities.length;
            for (var i = 0; i < numEntities; i++) {
                var entity = this._entities[i];
                entity.t += this._speed * entity.relSpeed * this._speedMultiplier / this._stdDuration;
                entity.frame++;
                if (entity.frame >= this._hitRange) {
                    if (dispatchEvent) {
                        // dispatch a judgement event
                    }
                    entity.kill();
                    this._entities.splice(i, 1);
                    numEntities--;
                    i--;
                }
            }
        }
    },
    "_createEntities": {
        "value": function (currentFrame) {
            while (this._noteIndex < this._source.notes.length
                && this._source.notes[this._noteIndex].appearFrame === currentFrame) {
                if (this._entityBuilder) {
                    var note   = this._source.notes[this._noteIndex];
                    var entity = this._entityBuilder.createNoteEntity(
                        -1.0 + note.posCorrection,
                        note.relSpeed,
                        note.appearFrame - note.hitFrame,
                        cloneObject(note.properties)
                    );
                    this._insertEntity(entity);
                }
                this._noteIndex++;
            }
        }
    },
    "_insertEntity": {
        "value": function (entity) {
            for (var i = 0; i < this._entities.length; i++) {
                if (entity.frame > this._entities[i].frame) {
                    this._entities.splice(i, 0, entity);
                    return;
                }
            }
            this._entities.push(entity);
        }
    },
    "_changeSpeed": {
        "value": function (currentFrame) {
            while (this._speedChangeIndex < this._source.speedChanges.length
                && this._source.speedChanges[this._speedChangeIndex].frame === currentFrame) {
                this._speed = this._source.speedChanges[this._speedChangeIndex].speed;
                this._speedChangeIndex++;
            }
        }
    },
    "_changeProperties": {
        "value": function (currentFrame) {
            while (this._propertyChangeIndex < this._source.propertyChanges.length
                && this._source.propertyChanges[this._propertyChangeIndex].frame === currentFrame) {
                var propertyChange = this._source.propertyChanges[this._propertyChangeIndex];
                for (var i = 0; i < _entities.length; i++) {
                    _entities.setProperty(propertyChange.name, propertyChange.value);
                }
                this._propertyChangeIndex++;
            }
        }
    }
});

function LongNoteThread(source, inputDevice, hitRange, stdDuration, speedMultiplier, entityBuilder) {
    this._source          = source;
    this._inputDevice     = inputDevice;
    this._hitRange        = hitRange;
    this._stdDuration     = stdDuration;
    this._speedMultiplier = speedMultiplier;
    this._entityBuilder   = entityBuilder;
}

LongNoteThread.prototype = Object.create(comet.thread.IThread.prototype, {
    "constructor": {
        "value"       : LongNoteThread,
        "writable"    : true,
        "configurable": true
    }
});

Object.defineProperties(LongNoteThread.prototype, {
    "init": {
        "value": function (initialFrame) {

        }
    },
    "tick": {
        "value": function (currentFrame) {

        }
    }
});

function Source(noteSource, longNoteSource) {
    this.noteSource     = noteSource;
    this.longNoteSource = longNoteSource;
}

Object.defineProperties(Source.prototype, {
    "clone": {
        "value": function () {
            return new Source(this.noteSource.clone(), this.longNoteSource.clone());
        }
    },
    "init": {
        "value": function (stdDuration, speedMultiplier) {
            this.noteSource.init(stdDuration, speedMultiplier);
            this.longNoteSource.init(stdDuration, speedMultiplier);
        }
    }
});

function NoteSource(notes, speedChanges, propertyChanges) {
    this.notes           = notes;
    this.speedChanges    = speedChanges;
    this.propertyChanges = propertyChanges;
}

Object.defineProperties(NoteSource.prototype, {
    "clone": {
        "value": function () {
            return new LongNoteSource(
                this.notes.map(function (note) { return note.clone(); }),
                this.speedChanges.map(function (speedChange) { return speedChange.clone(); }),
                this.propertyChanges.map(function (propertyChange) { return propertyChange.clone(); })

            );
        }
    },
    "init": {
        "value": function (stdDuration, speedMultiplier) {
            this.speedChanges    = comet.util.stableSort(this.speedChanges, byFrames);
            this.propertyChanges = comet.util.stableSort(this.propertyChanges, byFrames);

            this.notes = comet.util.stableSort(this.notes, byHitFrames);

            var numSpeedChanges = this.speedChanges.length;
            var numNotes        = this.notes.length;

            var noteIndex;
            var speedChangeIndex = 0;

            for (noteIndex = 0; noteIndex < numNotes; noteIndex++) {
                var note = this.notes[noteIndex];

                while (speedChangeIndex < 0 || (speedChangeIndex < numSpeedChanges
                    && this.speedChanges[speedChangeIndex].frame < note.hitFrame)) {
                    speedChangeIndex++;
                }
                speedChangeIndex--;

                var currentSpeed    = speedChangeIndex < 0 ? 1.0 : this.speedChanges[speedChangeIndex].speed;
                var currentFrame    = note.hitFrame;
                var currentPosition = 0.0;

                while (currentPosition > -1.0) {
                    currentFrame--;

                    while (speedChangeIndex >= 0 && currentFrame < this.speedChanges[speedChangeIndex].frame) {
                        speedChangeIndex--;

                        if (speedChangeIndex < 0) {
                            currentSpeed = 1.0
                        }
                        else {
                            currentSpeed = this.speedChanges[speedChangeIndex].speed;
                        }
                    }

                    currentPosition -= currentSpeed * note.relSpeed * speedMultiplier / stdDuration;
                }

                note.appearFrame   = currentFrame;
                note.posCorrection = currentPos + 1.0;
            }

            this.notes = comet.util.stableSort(this.notes, byAppearFrames);
        }
    }
});

function LongNoteSource(longNotes, speedChanges, propertyChanges) {
    this.longNotes       = longNotes;
    this.speedChanges    = speedChanges;
    this.propertyChanges = propertyChanges;
}

Object.defineProperties(LongNoteSource.prototype, {
    "clone": {
        "value": function () {
            return new LongNoteSource(
                this.longNotes.map(function (longNote) { return longNote.clone(); }),
                this.speedChanges.map(function (speedChange) { return speedChange.clone(); }),
                this.propertyChanges.map(function (propertyChange) { return propertyChange.clone(); })

            );
        }
    },
    "init": {
        "value": function (stdDuration, speedMultiplier) {
            this.speedChanges    = comet.util.stableSort(this.speedChanges, byFrames);
            this.propertyChanges = comet.util.stableSort(this.propertyChanges, byFrames);

            this.longNotes = comet.util.stableSort(this.longNotes, byHitFrames);

            var numSpeedChanges = this.speedChanges.length;
            var numLongNotes    = this.longNotes.length;

            var longNoteIndex;
            var speedChangeIndex = 0;

            for (longNoteIndex = 0; longNoteIndex < numLongNotes; longNoteIndex++) {
                var longNote = this.longNotes[longNoteIndex];

                while (speedChangeIndex < 0 || (speedChangeIndex < numSpeedChanges
                    && this.speedChanges[speedChangeIndex].frame < longNote.endFrame)) {
                    speedChangeIndex++;
                }
                speedChangeIndex--;

                var currentSpeed    = speedChangeIndex < 0 ? 1.0 : this.speedChanges[speedChangeIndex].speed;
                var currentFrame    = longNote.endFrame;
                var currentPosition = 0.0;
                var currentLength   = 0.0;

                while (currentPosition > -1.0) {
                    currentFrame--;

                    while (speedChangeIndex >= 0 && currentFrame < this.speedChanges[speedChangeIndex].frame) {
                        speedChangeIndex--;

                        if (speedChangeIndex < 0) {
                            currentSpeed = 1.0;
                        }
                        else {
                            currentSpeed = this.speedChanges[speedChangeIndex].speed;
                        }
                    }

                    if (longNote.hitFrame <= currentFrame) {
                        currentLength += currentSpeed * longNote.relSpeed * speedMultiplier / stdDuration;
                    }
                    else {
                        currentPosition -= currentSpeed * longNote.relSpeed * speedMultiplier / stdDuration;
                    }
                }

                longNote.appearFrame   = currentFrame;
                longNote.length        = currentLength;
                longNote.posCorrection = currentPosition + 1.0;
            }

            this.longNotes = comet.util.stableSort(this.longNotes, byAppearFrames);
        }
    }
});

function Note(hitFrame, relSpeed, properties) {
    this.hitFrame      = hitFrame;
    this.relSpeed      = relSpeed;
    this.appearFrame   = 0;
    this.posCorrection = 0;
    this.properties    = cloneObject(properties);
}

Object.defineProperties(Note.prototype, {
    "clone": {
        "value": function () {
            var note = new Note(this.hitFrame, this.relSpeed, cloneObject(this.properties));
            note.appearFrame   = this.appearFrame;
            note.posCorrection = this.posCorrection;
            return note;
        }
    }
});

function LongNote(hitFrame, endFrame, relSpeed, properties) {
    this.hitFrame      = hitFrame;
    this.endFrame      = endFrame;
    this.relSpeed      = relSpeed;
    this.appearFrame   = 0;
    this.length        = 0;
    this.posCorrection = 0;
    this.properties    = cloneObject(properties);
}

Object.defineProperties(LongNote.prototype, {
    "clone": {
        "value": function () {
            var longNote = new LongNote(this.hitFrame, this.endFrame, this.relSpeed, cloneObject(this.properties));
            longNote.appearFrame   = this.appearFrame;
            longNote.length        = this.length;
            longNote.posCorrection = this.posCorrection;
            return longNote;
        }
    }
});

function SpeedChange(frame, speed) {
    this.frame = frame;
    this.speed = speed;
}

Object.defineProperties(SpeedChange.prototype, {
    "clone": {
        "value": function () {
            return new SpeedChange(this.frame, this.speed);
        }
    }
});

function PropertyChange(frame, name, value) {
    this.frame = frame;
    this.name  = name;
    this.value = value;
}

Object.defineProperties(PropertyChange.prototype, {
    "clone": {
        "value": function () {
            return new PropertyChange(this.frame, this.name, this.value);
        }
    }
});

function byFrames(x, y) {
    return x.frame - y.frame;
}

function byHitFrames(x, y) {
    return x.hitFrame - y.hitFrame;
}

function byAppearFrames(x, y) {
    return x.appearFrame - y.appearFrame;
}

function cloneObject(object) {
    if (typeof object !== "object" || object === null) {
        return object;
    }
    else {
        var copy = {};
        for (var key in object) {
            if (object.hasOwnProperty(key)) {
                copy[key] = object[key];
            }
        }
        return copy;
    }
}

end();
<|endoftext|>


<commit_before>'use strict'
const log = require('./log')
const querystring = require('querystring')
const rp = require('request-promise')
const settings = require('./configuration').server
const CanvasApi = require('kth-canvas-api')
const csv = require('./csvFile')
const ldap = require('./ldap')
const moment = require('moment')
const _ = require('lodash')
const canvasApiUrl = `https://${settings.canvas.host}/api/v1`

function exportResults (req, res) {
  try {
    let b = req.body
    log.info(`The user ${b.lis_person_sourcedid}, ${b.custom_canvas_user_login_id}, is exporting the course ${b.context_label} with id ${b.custom_canvas_course_id}`)
    let courseRound = b.lis_course_offering_sourcedid
    const canvasCourseId = b.custom_canvas_course_id
    const fullUrl = (settings.proxyBase || (req.protocol + '://' + req.get('host'))) + req.originalUrl
    const nextUrl = fullUrl + '2?' + querystring.stringify({courseRound, canvasCourseId})
    log.info('Tell auth to redirect back to', nextUrl)
    log.info('using canvas client id', settings.canvas.clientId)
    const basicUrl = `https://${settings.canvas.host}/login/oauth2/auth?` + querystring.stringify({client_id: settings.canvas.clientId, response_type: 'code', redirect_uri: nextUrl})
    res.redirect(basicUrl)
  } catch (e) {
    log.error('Export failed:', e)
    res.status(500).send(`<link rel="stylesheet" href="/api/lms-export-results/kth-style/css/kth-bootstrap.css">

Smth has gone wrong, try it later.
`)
  }
}

async function getAccessToken ({clientId, clientSecret, redirectUri, code}) {
  const auth = await rp({
    method: 'POST',
    uri: `https://${settings.canvas.host}/login/oauth2/token`,
    body: {
      grant_type: 'authorization_code',
      client_id: clientId,
      client_secret: clientSecret,
      redirect_uri: redirectUri,
      code
    },
    json: true
  })
  return auth.access_token
}

async function getAssignmentIdsAndHeaders ({canvasApi, canvasCourseId}) {
  const assignmentIds = []
  const headers = {}

  const assignments = await canvasApi.recursePages(`${canvasApiUrl}/courses/${canvasCourseId}/assignments`)

  for (let t of assignments) {
    const id = '' + t.id
    assignmentIds.push(id)
    headers[id] = `${t.name} (${t.id})`
  }
  return {assignmentIds, headers}
}

async function createFixedColumnsContent ({student, ldapClient, section, canvasUser}) {
  let row
  try {
    const ugUser = await ldap.lookupUser(ldapClient, student.sis_user_id)
    row = {
      kthid: student.sis_user_id,
      givenName: ugUser.givenName,
      surname: ugUser.sn,
      personnummer: ugUser.norEduPersonNIN
    }
  } catch (err) {
    log.error('An error occured while trying to find user in ldap:', err)
    log.info('No user from ldap, use empty row instead')
    row = {}
  }

  return [
    student.sis_user_id || '',
    student.user_id || '',
    section.name || '',
    row.givenName || '',
    row.surname || '',
    `="${row.personnummer || ''}"`,
    (canvasUser && canvasUser.login_id) || ''
  ]
}

function createCustomColumnsContent ({customColumnsData, customColumns}) {
  const sortedCustomColumns = _.orderBy(customColumns, ['position'], ['asc'])
  return sortedCustomColumns.map(customColumn => customColumnsData[customColumn.id] || '')
}

function createSubmissionLineContent ({student, assignmentIds}) {
  const row = {}
  for (let submission of student.submissions) {
    row['' + submission.assignment_id] = submission.entered_grade || ''
  }
  return assignmentIds.map(id => row[id] || '-')
}

async function createCsvLineContent ({student, ldapClient, assignmentIds, section, canvasUser, customColumns, customColumnsData}) {
  const fixedColumnsContent = await createFixedColumnsContent({student, ldapClient, assignmentIds, section, canvasUser})
  const customColumnsContent = createCustomColumnsContent({customColumnsData, customColumns})
  const assignmentsColumnsContent = createSubmissionLineContent({student, ldapClient, assignmentIds, section, canvasUser})

  return [
    ...fixedColumnsContent,
    ...customColumnsContent,
    ...assignmentsColumnsContent
  ]
}

function exportResults2 (req, res) {
  try {
    // Hack to make Canvas see that the auth is finished and the
    // 'please wait' text can be removed
    res.send(`
    <link rel="stylesheet" href="/api/lms-export-results/kth-style/css/kth-bootstrap.css">

Your download should start automatically. If nothing happens within a few minutes, please go back and try again.

      `)
  } catch (e) {
    log.error('Export failed:', e)
    res.status(500).send(`<link rel="stylesheet" href="/api/lms-export-results/kth-style/css/kth-bootstrap.css">

Smth has gone wrong, try it later.
`)
  }
}

function exportDone (req, res) {
  res.send(`<link rel="stylesheet" href="/api/lms-export-results/kth-style/css/kth-bootstrap.css">

Done. The file should now be downloaded to your computer.
`)
}

async function curriedIsFake ({canvasApi, canvasApiUrl, canvasCourseId}) {
  const fakeUsers = await canvasApi.recursePages(`${canvasApiUrl}/courses/${canvasCourseId}/users?enrollment_type[]=student_view`)
  return function (student) {
    return fakeUsers.find(user => user.id === student.user_id)
  }
}

async function getCustomColumnsFn ({canvasApi, canvasCourseId, canvasApiUrl}) {
  const customColumnsData = {}
  const customColumns = await canvasApi.recursePages(`${canvasApiUrl}/courses/${canvasCourseId}/custom_gradebook_columns`)
  for (let customColumn of customColumns) {
    const data = await canvasApi.recursePages(`${canvasApiUrl}/courses/${canvasCourseId}/custom_gradebook_columns/${customColumn.id}/data`)
    for (let dataEntry of data) {
      customColumnsData[dataEntry.user_id] = customColumnsData[dataEntry.user_id] || {}
      customColumnsData[dataEntry.user_id][customColumn.id] = dataEntry.content
    }
  }
  return {
    customColumns,
    getCustomColumnsData (userId) {
      return customColumnsData[userId] || {}
    }
  }
}

function getCustomColumnHeaders(customColumns){
  return  _.orderBy(customColumns, ['position'], ['asc']).map(c => c.title)
}

async function exportResults3 (req, res) {
  try {
    // log.info('1')
    const fetchedSections = {}
    // log.info('2')
    const courseRound = req.query.courseRound
    // log.info('3')
    const canvasCourseId = req.query.canvasCourseId
    // log.info('4')
    log.info(`Should export for ${courseRound} / ${canvasCourseId}`)
    // Start writing response as soon as possible
    res.set({
      'content-type': 'text/csv; charset=utf-8',
      'location': 'http://www.kth.se'
    })
    // log.info('5')
    res.attachment(`${courseRound || 'canvas'}-${moment().format("YYYYMMDD-HHMMSS")}-results.csv`)
    // log.info('6')
    // Write BOM https://sv.wikipedia.org/wiki/Byte_order_mark
    res.write('\uFEFF')
    // log.info('7')

    const accessToken = await getAccessToken({
      clientId: settings.canvas.clientId,
      clientSecret: settings.canvas.clientSecret,
      redirectUri: req.protocol + '://' + req.get('host') + req.originalUrl,
      code: req.query.code
    })
    // log.info('8')
    const canvasApi = new CanvasApi(canvasApiUrl, accessToken)
    // log.info('9')
    // So far so good, start constructing the output
    const {assignmentIds, headers} = await getAssignmentIdsAndHeaders({canvasApi, canvasCourseId})
    // log.info('10')
    const {getCustomColumnsData, customColumns} = await getCustomColumnsFn({canvasApi, canvasCourseId, canvasApiUrl})
    // log.info('11')
    const fixedColumnHeaders = [
      'SIS User ID',
      'ID',
      'Section',
      'Name',
      'Surname',
      'Personnummer',
      'Email address']
      // log.info('12')
    // Note that the order of these columns has to match that returned from the 'createCsvLineContent' function
    const csvHeader = [
      ...fixedColumnHeaders,
      ...getCustomColumnHeaders(customColumns),
      ...assignmentIds.map(id => headers[id])
    ]
    // log.info('13')
    res.write(csv.createLine(csvHeader))
    // log.info('14')
    const ldapClient = await ldap.getBoundClient()

    const students = await canvasApi.recurse(`courses/${canvasCourseId}/students/submissions?grouped=1&student_ids[]=all`)

    // TODO: the following endpoint is deprecated. Change when Instructure has responded on how we should query instead.
    const usersInCourse = await canvasApi.requestUrl(`courses/${canvasCourseId}/students`)

    const isFake = await curriedIsFake({canvasApi, canvasApiUrl, canvasCourseId})

    for (let student of students) {
      if (isFake(student)) {
        continue
      }
      const section = fetchedSections[student.section_id] || await canvasApi.requestCanvas(`sections/${student.section_id}`)
      fetchedSections[student.section_id] = section

      const canvasUser = usersInCourse.find(u => u.id === student.user_id)

      const customColumnsData = getCustomColumnsData(student.user_id)
      const csvLine = await createCsvLineContent({
        student,
        ldapClient,
        assignmentIds,
        section,
        canvasUser,
        customColumns,
        customColumnsData})

      res.write(csv.createLine(csvLine))
    }
    res.send()
    await ldapClient.unbind()
  } catch (e) {
    log.error(`Export failed for query ${req.query}:`, e)
    res.status(500).send(`

                        Åh nej, något gick fel!

                    Försök gärna igen, så håller vi tummarna att det går bättre nästa gång...

Fel:

                  ${e.message}

      `)
  }
}

module.exports = {
  // Do not remove the System controller!
  // System: require('./controllers/systemCtrl'),

  exportResults,
  exportResults2,
  exportResults3,
  exportDone
}
<commit_msg>removed commented out code<commit_after>'use strict'
const log = require('./log')
const querystring = require('querystring')
const rp = require('request-promise')
const settings = require('./configuration').server
const CanvasApi = require('kth-canvas-api')
const csv = require('./csvFile')
const ldap = require('./ldap')
const moment = require('moment')
const _ = require('lodash')
const canvasApiUrl = `https://${settings.canvas.host}/api/v1`

function exportResults (req, res) {
  try {
    let b = req.body
    log.info(`The user ${b.lis_person_sourcedid}, ${b.custom_canvas_user_login_id}, is exporting the course ${b.context_label} with id ${b.custom_canvas_course_id}`)
    let courseRound = b.lis_course_offering_sourcedid
    const canvasCourseId = b.custom_canvas_course_id
    const fullUrl = (settings.proxyBase || (req.protocol + '://' + req.get('host'))) + req.originalUrl
    const nextUrl = fullUrl + '2?' + querystring.stringify({courseRound, canvasCourseId})
    log.info('Tell auth to redirect back to', nextUrl)
    log.info('using canvas client id', settings.canvas.clientId)
    const basicUrl = `https://${settings.canvas.host}/login/oauth2/auth?` + querystring.stringify({client_id: settings.canvas.clientId, response_type: 'code', redirect_uri: nextUrl})
    res.redirect(basicUrl)
  } catch (e) {
    log.error('Export failed:', e)
    res.status(500).send(`<link rel="stylesheet" href="/api/lms-export-results/kth-style/css/kth-bootstrap.css">

Smth has gone wrong, try it later.
`)
  }
}

async function getAccessToken ({clientId, clientSecret, redirectUri, code}) {
  const auth = await rp({
    method: 'POST',
    uri: `https://${settings.canvas.host}/login/oauth2/token`,
    body: {
      grant_type: 'authorization_code',
      client_id: clientId,
      client_secret: clientSecret,
      redirect_uri: redirectUri,
      code
    },
    json: true
  })
  return auth.access_token
}

async function getAssignmentIdsAndHeaders ({canvasApi, canvasCourseId}) {
  const assignmentIds = []
  const headers = {}

  const assignments = await canvasApi.recursePages(`${canvasApiUrl}/courses/${canvasCourseId}/assignments`)

  for (let t of assignments) {
    const id = '' + t.id
    assignmentIds.push(id)
    headers[id] = `${t.name} (${t.id})`
  }
  return {assignmentIds, headers}
}

async function createFixedColumnsContent ({student, ldapClient, section, canvasUser}) {
  let row
  try {
    const ugUser = await ldap.lookupUser(ldapClient, student.sis_user_id)
    row = {
      kthid: student.sis_user_id,
      givenName: ugUser.givenName,
      surname: ugUser.sn,
      personnummer: ugUser.norEduPersonNIN
    }
  } catch (err) {
    log.error('An error occured while trying to find user in ldap:', err)
    log.info('No user from ldap, use empty row instead')
    row = {}
  }

  return [
    student.sis_user_id || '',
    student.user_id || '',
    section.name || '',
    row.givenName || '',
    row.surname || '',
    `="${row.personnummer || ''}"`,
    (canvasUser && canvasUser.login_id) || ''
  ]
}

function createCustomColumnsContent ({customColumnsData, customColumns}) {
  const sortedCustomColumns = _.orderBy(customColumns, ['position'], ['asc'])
  return sortedCustomColumns.map(customColumn => customColumnsData[customColumn.id] || '')
}

function createSubmissionLineContent ({student, assignmentIds}) {
  const row = {}
  for (let submission of student.submissions) {
    row['' + submission.assignment_id] = submission.entered_grade || ''
  }
  return assignmentIds.map(id => row[id] || '-')
}

async function createCsvLineContent ({student, ldapClient, assignmentIds, section, canvasUser, customColumns, customColumnsData}) {
  const fixedColumnsContent = await createFixedColumnsContent({student, ldapClient, assignmentIds, section, canvasUser})
  const customColumnsContent = createCustomColumnsContent({customColumnsData, customColumns})
  const assignmentsColumnsContent = createSubmissionLineContent({student, ldapClient, assignmentIds, section, canvasUser})

  return [
    ...fixedColumnsContent,
    ...customColumnsContent,
    ...assignmentsColumnsContent
  ]
}

function exportResults2 (req, res) {
  try {
    // Hack to make Canvas see that the auth is finished and the
    // 'please wait' text can be removed
    res.send(`
    <link rel="stylesheet" href="/api/lms-export-results/kth-style/css/kth-bootstrap.css">

Your download should start automatically. If nothing happens within a few minutes, please go back and try again.

      `)
  } catch (e) {
    log.error('Export failed:', e)
    res.status(500).send(`<link rel="stylesheet" href="/api/lms-export-results/kth-style/css/kth-bootstrap.css">

Smth has gone wrong, try it later.
`)
  }
}

function exportDone (req, res) {
  res.send(`<link rel="stylesheet" href="/api/lms-export-results/kth-style/css/kth-bootstrap.css">

Done. The file should now be downloaded to your computer.
`)
}

async function curriedIsFake ({canvasApi, canvasApiUrl, canvasCourseId}) {
  const fakeUsers = await canvasApi.recursePages(`${canvasApiUrl}/courses/${canvasCourseId}/users?enrollment_type[]=student_view`)
  return function (student) {
    return fakeUsers.find(user => user.id === student.user_id)
  }
}

async function getCustomColumnsFn ({canvasApi, canvasCourseId, canvasApiUrl}) {
  const customColumnsData = {}
  const customColumns = await canvasApi.recursePages(`${canvasApiUrl}/courses/${canvasCourseId}/custom_gradebook_columns`)
  for (let customColumn of customColumns) {
    const data = await canvasApi.recursePages(`${canvasApiUrl}/courses/${canvasCourseId}/custom_gradebook_columns/${customColumn.id}/data`)
    for (let dataEntry of data) {
      customColumnsData[dataEntry.user_id] = customColumnsData[dataEntry.user_id] || {}
      customColumnsData[dataEntry.user_id][customColumn.id] = dataEntry.content
    }
  }
  return {
    customColumns,
    getCustomColumnsData (userId) {
      return customColumnsData[userId] || {}
    }
  }
}

function getCustomColumnHeaders(customColumns){
  return  _.orderBy(customColumns, ['position'], ['asc']).map(c => c.title)
}

async function exportResults3 (req, res) {
  try {
    const fetchedSections = {}
    const courseRound = req.query.courseRound
    const canvasCourseId = req.query.canvasCourseId
    log.info(`Should export for ${courseRound} / ${canvasCourseId}`)
    // Start writing response as soon as possible
    res.set({
      'content-type': 'text/csv; charset=utf-8',
      'location': 'http://www.kth.se'
    })
    res.attachment(`${courseRound || 'canvas'}-${moment().format("YYYYMMDD-HHMMSS")}-results.csv`)
    // Write BOM https://sv.wikipedia.org/wiki/Byte_order_mark
    res.write('\uFEFF')

    const accessToken = await getAccessToken({
      clientId: settings.canvas.clientId,
      clientSecret: settings.canvas.clientSecret,
      redirectUri: req.protocol + '://' + req.get('host') + req.originalUrl,
      code: req.query.code
    })
    const canvasApi = new CanvasApi(canvasApiUrl, accessToken)
    // So far so good, start constructing the output
    const {assignmentIds, headers} = await getAssignmentIdsAndHeaders({canvasApi, canvasCourseId})
    const {getCustomColumnsData, customColumns} = await getCustomColumnsFn({canvasApi, canvasCourseId, canvasApiUrl})
    const fixedColumnHeaders = [
      'SIS User ID',
      'ID',
      'Section',
      'Name',
      'Surname',
      'Personnummer',
      'Email address']
    // Note that the order of these columns has to match that returned from the 'createCsvLineContent' function
    const csvHeader = [
      ...fixedColumnHeaders,
      ...getCustomColumnHeaders(customColumns),
      ...assignmentIds.map(id => headers[id])
    ]
    res.write(csv.createLine(csvHeader))
    const ldapClient = await ldap.getBoundClient()

    const students = await canvasApi.recurse(`courses/${canvasCourseId}/students/submissions?grouped=1&student_ids[]=all`)

    // TODO: the following endpoint is deprecated. Change when Instructure has responded on how we should query instead.
    const usersInCourse = await canvasApi.requestUrl(`courses/${canvasCourseId}/students`)

    const isFake = await curriedIsFake({canvasApi, canvasApiUrl, canvasCourseId})

    for (let student of students) {
      if (isFake(student)) {
        continue
      }
      const section = fetchedSections[student.section_id] || await canvasApi.requestCanvas(`sections/${student.section_id}`)
      fetchedSections[student.section_id] = section

      const canvasUser = usersInCourse.find(u => u.id === student.user_id)

      const customColumnsData = getCustomColumnsData(student.user_id)
      const csvLine = await createCsvLineContent({
        student,
        ldapClient,
        assignmentIds,
        section,
        canvasUser,
        customColumns,
        customColumnsData})

      res.write(csv.createLine(csvLine))
    }
    res.send()
    await ldapClient.unbind()
  } catch (e) {
    log.error(`Export failed for query ${req.query}:`, e)
    res.status(500).send(`

                        Åh nej, något gick fel!

                    Försök gärna igen, så håller vi tummarna att det går bättre nästa gång...

Fel:

                  ${e.message}

      `)
  }
}

module.exports = {
  // Do not remove the System controller!
  // System: require('./controllers/systemCtrl'),

  exportResults,
  exportResults2,
  exportResults3,
  exportDone
}
<|endoftext|>


<commit_before>
$(document).ready(function() {

	$.getJSON('json/server.json?n=1', function(data) {
		add(data, 'server');
		server = data;
		cli.storage = server;
		cli.storage.save = function() {};
	});
	$.getJSON('json/comp1.json?n=1', function(data) {
		add(data, 'client1');
		comp1 = data;
	});
	$.getJSON('json/comp2.json?n=1', function(data) {
		add(data, 'client2');
		comp2 = data;
	});

});

function add(obj, column) {
	$('#' + column + ' pre').html(JSON.stringify(obj, null, 4))
}

function clone(input) {
	return JSON.parse(JSON.stringify(input));
}

function get(comp, el) {

	// If computer has never been synced before
	if(comp.prefs.hasOwnProperty('synced')) {

		// Loop through each task
		for(var task in comp.tasks) {

			// Does not sync the length key
			if(task != 'length') {

				// Mess with task id's
				comp.tasks[parseInt(task) + server.tasks.length] = clone(comp.tasks[task]);
				delete comp.tasks[task]

			}
		}

		// Remove the synced property
		delete comp.prefs.synced;
	}

	// Loop through each list
	for(var list in comp.lists.items) {

		// Check to see if list exist on server
		if(!server.lists.items.hasOwnProperty(list)) {

			// If the list doesn't exist on the server, create it
			server.lists.items[list] = {
				name: "",
				order: []
			}

			// Copy the name over // TODO: Add timestamps to the list name
			server.lists.items[list].name = comp.lists.items[list].name;

		}
	}

	// Loop through each task
	for(var task in comp.tasks) {

		// Do not sync the tasks.length propery
		// This should only be modified by the server side cli.js
		if(task != 'length') {

			// If task does not exist on the server
			if(!server.tasks.hasOwnProperty(task)) {

				// If task hasn't been deleted
				if(!comp.tasks[task].hasOwnProperty('deleted')) {

					// Add the task to the server
					cli.addTask(comp.tasks[task].content, comp.tasks[task].list);
					server.tasks[task] = clone(comp.tasks[task]);

					// Calculate date
					cli.calc.date(task);

					// Calculate Today etc? - Do later
					cli.today(task).calculate();

				// The task is new, but the client deleted it
				} else {

					// Add the task to the server, but don't touch lists and stuff
					server.tasks[task] = clone(comp.tasks[task]);

				}

			// Task was deleted on computer but not on the server
			} else if(comp.tasks[task].hasOwnProperty('deleted') && !server.tasks[task].hasOwnProperty('deleted')) {

				// We use this to check whether the task was modified AFTER it was deleted
				var deleteTask = true;

				// Loop through each attribute on server
				for(var key in server.tasks[task]) {

					// Check if server task was modified after task was deleted
					if(server.tasks[task].time[key] > comp.tasks[task].deleted) {

						// Since it has been modified after it was deleted, we don't delete the task
						deleteTask = false;

					}
				}

				// If there have been no modifications to the task after it has been deleted
				if(deleteTask) {

					// Delete the task
					cli.deleteTask(task);

					// Get the timestamp
					server.tasks[task] = clone(comp.tasks[task]);

					// Update stuff
					// cli.calc.date(task);
					// cli.today(task).calculate();

				}

			// Task is deleted on the server and the computer
			} else if(comp.tasks[task].hasOwnProperty('deleted') && server.tasks[task].hasOwnProperty('deleted')){

				// Use the latest time stamp
				if(comp.tasks[task].deleted > server.tasks[task].deleted) {

					// If the task was deleted on a computer after it was deleted on the server, then update the time stamp
					server.tasks[task].deleted = comp.tasks[task].deleted;

				}

			} else {

				//Stores the Attrs we'll be needing later
				var changedAttrs = [];

				// Loop through each attribute on computer
				for(var key in comp.tasks[task]) {

					//Don't loop through timestamps
					if (key != 'time') {

						// Check if task was deleted on server or
						 if (server.tasks[task].hasOwnProperty('deleted')) {

							// Check if task was modified after it was deleted
							if(comp.tasks[task].time[key] > server.tasks[task].deleted) {

								// Update the server with the entire task (including attributes and timestamps)
								server.tasks[task] = comp.tasks[task];

								//Breaks, we only need to do the thing once.
								break;
							}

						// Task has not been deleted
						} else {

							// If the attribute was updated after the server
							if(comp.tasks[task].time[key] > server.tasks[task].time[key]) {

								if (key != 'list') {
									// Update the servers version
									server.tasks[task][key] = comp.tasks[task][key];
								}

								// Update the timestamp
								server.tasks[task].time[key] = comp.tasks[task].time[key];

								//Adds the changed Attr to the array
								changedAttrs.push(key);
							}
						}
					}
				}

				if (changedAttrs.length > 0) {
					if(changedAttrs.indexOf('logged') != -1) {
						// Logged
						console.log("The logged one was changed", task);
						cli.logbook(task)
						cli.logbook(task)
					} else if(changedAttrs.indexOf('date') != -1 || changedAttrs.indexOf('showInToday') != -1) {
						// Date is changed
						console.log('The date was changed');
						cli.calc.date(task);
						cli.today(task).calculate();
					} else if(changedAttrs.indexOf('today') != -1) {
						// Today
						console.log('Today was changed');
						cli.today(task).calculate();
					}

					if(changedAttrs.indexOf('list') != -1) {
						// List
						console.log('The list was changed')
						cli.moveTask(task, comp.tasks[task].list)
					}
				}
			}
		}
	}

	// Update length
	server.tasks.length = 0;
	for (i in server.tasks) {
		if (server.tasks.hasOwnProperty(i) && i != 'length') {
			server.tasks.length++;
		}
	}

	// Print to UI
	add(server, 'server');
	add(server, el);

}<commit_msg>Update list name (not tested)<commit_after>
$(document).ready(function() {

	$.getJSON('json/server.json?n=1', function(data) {
		add(data, 'server');
		server = data;
		cli.storage = server;
		cli.storage.save = function() {};
	});
	$.getJSON('json/comp1.json?n=1', function(data) {
		add(data, 'client1');
		comp1 = data;
	});
	$.getJSON('json/comp2.json?n=1', function(data) {
		add(data, 'client2');
		comp2 = data;
	});

});

function add(obj, column) {
	$('#' + column + ' pre').html(JSON.stringify(obj, null, 4))
}

function clone(input) {
	return JSON.parse(JSON.stringify(input));
}

function get(comp, el) {

	// If computer has never been synced before
	if(comp.prefs.hasOwnProperty('synced')) {

		// Loop through each task
		for(var task in comp.tasks) {

			// Does not sync the length key
			if(task != 'length') {

				// Mess with task id's
				comp.tasks[parseInt(task) + server.tasks.length] = clone(comp.tasks[task]);
				delete comp.tasks[task]

			}
		}

		// Remove the synced property
		delete comp.prefs.synced;
	}

	// Loop through each list
	for(var list in comp.lists.items) {

		// Check to see if list exist on server
		if(!server.lists.items.hasOwnProperty(list)) {

			// If the list doesn't exist on the server, create it
			server.lists.items[list] = {
				name: "",
				order: []
			}

			// Copy the name over // TODO: Add timestamps to the list name
			server.lists.items[list].name = comp.lists.items[list].name;

		// Check to see if list was updated
		} else if(comp.lists.items[id].time > server.lists.items[id].time) {

			// If so, update list name
			server.lists.items[id].name = comp.lists.items[id].name;

		}
	}

	// Loop through each task
	for(var task in comp.tasks) {

		// Do not sync the tasks.length propery
		// This should only be modified by the server side cli.js
		if(task != 'length') {

			// If task does not exist on the server
			if(!server.tasks.hasOwnProperty(task)) {

				// If task hasn't been deleted
				if(!comp.tasks[task].hasOwnProperty('deleted')) {

					// Add the task to the server
					cli.addTask(comp.tasks[task].content, comp.tasks[task].list);
					server.tasks[task] = clone(comp.tasks[task]);

					// Calculate date
					cli.calc.date(task);

					// Calculate Today etc? - Do later
					cli.today(task).calculate();

				// The task is new, but the client deleted it
				} else {

					// Add the task to the server, but don't touch lists and stuff
					server.tasks[task] = clone(comp.tasks[task]);

				}

			// Task was deleted on computer but not on the server
			} else if(comp.tasks[task].hasOwnProperty('deleted') && !server.tasks[task].hasOwnProperty('deleted')) {

				// We use this to check whether the task was modified AFTER it was deleted
				var deleteTask = true;

				// Loop through each attribute on server
				for(var key in server.tasks[task]) {

					// Check if server task was modified after task was deleted
					if(server.tasks[task].time[key] > comp.tasks[task].deleted) {

						// Since it has been modified after it was deleted, we don't delete the task
						deleteTask = false;

					}
				}

				// If there have been no modifications to the task after it has been deleted
				if(deleteTask) {

					// Delete the task
					cli.deleteTask(task);

					// Get the timestamp
					server.tasks[task] = clone(comp.tasks[task]);

					// Update stuff
					// cli.calc.date(task);
					// cli.today(task).calculate();

				}

			// Task is deleted on the server and the computer
			} else if(comp.tasks[task].hasOwnProperty('deleted') && server.tasks[task].hasOwnProperty('deleted')){

				// Use the latest time stamp
				if(comp.tasks[task].deleted > server.tasks[task].deleted) {

					// If the task was deleted on a computer after it was deleted on the server, then update the time stamp
					server.tasks[task].deleted = comp.tasks[task].deleted;

				}

			} else {

				//Stores the Attrs we'll be needing later
				var changedAttrs = [];

				// Loop through each attribute on computer
				for(var key in comp.tasks[task]) {

					//Don't loop through timestamps
					if (key != 'time') {

						// Check if task was deleted on server or
						 if (server.tasks[task].hasOwnProperty('deleted')) {

							// Check if task was modified after it was deleted
							if(comp.tasks[task].time[key] > server.tasks[task].deleted) {

								// Update the server with the entire task (including attributes and timestamps)
								server.tasks[task] = comp.tasks[task];

								//Breaks, we only need to do the thing once.
								break;
							}

						// Task has not been deleted
						} else {

							// If the attribute was updated after the server
							if(comp.tasks[task].time[key] > server.tasks[task].time[key]) {

								if (key != 'list') {
									// Update the servers version
									server.tasks[task][key] = comp.tasks[task][key];
								}

								// Update the timestamp
								server.tasks[task].time[key] = comp.tasks[task].time[key];

								//Adds the changed Attr to the array
								changedAttrs.push(key);
							}
						}
					}
				}

				if (changedAttrs.length > 0) {
					if(changedAttrs.indexOf('logged') != -1) {
						// Logged
						console.log("The logged one was changed", task);
						cli.logbook(task)
						cli.logbook(task)
					} else if(changedAttrs.indexOf('date') != -1 || changedAttrs.indexOf('showInToday') != -1) {
						// Date is changed
						console.log('The date was changed');
						cli.calc.date(task);
						cli.today(task).calculate();
					} else if(changedAttrs.indexOf('today') != -1) {
						// Today
						console.log('Today was changed');
						cli.today(task).calculate();
					}

					if(changedAttrs.indexOf('list') != -1) {
						// List
						console.log('The list was changed')
						cli.moveTask(task, comp.tasks[task].list)
					}
				}
			}
		}
	}

	// Update length
	server.tasks.length = 0;
	for (i in server.tasks) {
		if (server.tasks.hasOwnProperty(i) && i != 'length') {
			server.tasks.length++;
		}
	}

	// Print to UI
	add(server, 'server');
	add(server, el);

}<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var parser = require('socket.io-parser');
var Emitter = require('component-emitter');
var toArray = require('to-array');
var on = require('./on');
var bind = require('component-bind');
var debug = require('debug')('socket.io-client:socket');
var hasBin = require('has-binary-data');
var indexOf = require('indexof');

/**
 * Module exports.
 */

module.exports = exports = Socket;

/**
 * Internal events (blacklisted).
 * These events can't be emitted by the user.
 *
 * @api private
 */

var events = {
  connect: 1,
  connect_error: 1,
  connect_timeout: 1,
  disconnect: 1,
  error: 1,
  reconnect: 1,
  reconnect_attempt: 1,
  reconnect_failed: 1,
  reconnect_error: 1,
  reconnecting: 1
};

/**
 * Shortcut to `Emitter#emit`.
 */

var emit = Emitter.prototype.emit;

/**
 * `Socket` constructor.
 *
 * @api public
 */

function Socket(io, nsp){
  this.io = io;
  this.nsp = nsp;
  this.json = this; // compat
  this.ids = 0;
  this.acks = {};
  this.open();
  this.receiveBuffer = [];
  this.sendBuffer = [];
  this.connected = false;
  this.disconnected = true;
}

/**
 * Mix in `Emitter`.
 */

Emitter(Socket.prototype);

/**
 * Called upon engine `open`.
 *
 * @api private
 */

Socket.prototype.open =
Socket.prototype.connect = function(){
  if (this.connected) return this;
  var io = this.io;
  io.open(); // ensure open
  this.subs = [
    on(io, 'open', bind(this, 'onopen')),
    on(io, 'packet', bind(this, 'onpacket')),
    on(io, 'close', bind(this, 'onclose'))
  ];
  if ('open' == this.io.readyState) this.onopen();
  return this;
};

/**
 * Sends a `message` event.
 *
 * @return {Socket} self
 * @api public
 */

Socket.prototype.send = function(){
  var args = toArray(arguments);
  args.unshift('message');
  this.emit.apply(this, args);
  return this;
};

/**
 * Override `emit`.
 * If the event is in `events`, it's emitted normally.
 *
 * @param {String} event name
 * @return {Socket} self
 * @api public
 */

Socket.prototype.emit = function(ev){
  if (events.hasOwnProperty(ev)) {
    emit.apply(this, arguments);
    return this;
  }

  var args = toArray(arguments);
  var parserType = parser.EVENT; // default
  if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary
  var packet = { type: parserType, data: args };

  // event ack callback
  if ('function' == typeof args[args.length - 1]) {
    debug('emitting packet with ack id %d', this.ids);
    this.acks[this.ids] = args.pop();
    packet.id = this.ids++;
  }

  if (this.connected) {
    this.packet(packet);
  } else {
    this.sendBuffer.push(packet);
  }

  return this;
};

/**
 * Sends a packet.
 *
 * @param {Object} packet
 * @api private
 */

Socket.prototype.packet = function(packet){
  packet.nsp = this.nsp;
  this.io.packet(packet);
};

/**
 * "Opens" the socket.
 *
 * @api private
 */

Socket.prototype.onopen = function(){
  debug('transport is open - connecting');

  // write connect packet if necessary
  if ('/' != this.nsp) {
    this.packet({ type: parser.CONNECT });
  }
};

/**
 * Called upon engine `close`.
 *
 * @param {String} reason
 * @api private
 */

Socket.prototype.onclose = function(reason){
  debug('close (%s)', reason);
  this.connected = false;
  this.disconnected = true;
  this.emit('disconnect', reason);
};

/**
 * Called with socket packet.
 *
 * @param {Object} packet
 * @api private
 */

Socket.prototype.onpacket = function(packet){
  if (packet.nsp != this.nsp) return;

  switch (packet.type) {
    case parser.CONNECT:
      this.onconnect();
      break;

    case parser.EVENT:
      this.onevent(packet);
      break;

    case parser.BINARY_EVENT:
      this.onevent(packet);
      break;

    case parser.ACK:
      this.onack(packet);
      break;

    case parser.BINARY_ACK:
      this.onack(packet);
      break;

    case parser.DISCONNECT:
      this.ondisconnect();
      break;

    case parser.ERROR:
      this.emit('error', packet.data);
      break;
  }
};

/**
 * Called upon a server event.
 *
 * @param {Object} packet
 * @api private
 */

Socket.prototype.onevent = function(packet){
  var args = packet.data || [];
  debug('emitting event %j', args);

  if (null != packet.id) {
    debug('attaching ack callback to event');
    args.push(this.ack(packet.id));
  }

  if (this.connected) {
    emit.apply(this, args);
  } else {
    this.receiveBuffer.push(args);
  }
};

/**
 * Produces an ack callback to emit with an event.
 *
 * @api private
 */

Socket.prototype.ack = function(id){
  var self = this;
  var sent = false;
  return function(){
    // prevent double callbacks
    if (sent) return;
    sent = true;
    var args = toArray(arguments);
    debug('sending ack %j', args);

    var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK;
    self.packet({
      type: type,
      id: id,
      data: args
    });
  };
};

/**
 * Called upon a server acknowlegement.
 *
 * @param {Object} packet
 * @api private
 */

Socket.prototype.onack = function(packet){
  debug('calling ack %s with %j', packet.id, packet.data);
  var fn = this.acks[packet.id];
  fn.apply(this, packet.data);
  delete this.acks[packet.id];
};

/**
 * Called upon server connect.
 *
 * @api private
 */

Socket.prototype.onconnect = function(){
  this.connected = true;
  this.disconnected = false;
  this.emit('connect');
  this.emitBuffered();
};

/**
 * Emit buffered events (received and emitted).
 *
 * @api private
 */

Socket.prototype.emitBuffered = function(){
  var i;
  for (i = 0; i < this.receiveBuffer.length; i++) {
    emit.apply(this, this.receiveBuffer[i]);
  }
  this.receiveBuffer = [];

  for (i = 0; i < this.sendBuffer.length; i++) {
    this.packet(this.sendBuffer[i]);
  }
  this.sendBuffer = [];
};

/**
 * Called upon server disconnect.
 *
 * @api private
 */

Socket.prototype.ondisconnect = function(){
  debug('server disconnect (%s)', this.nsp);
  this.destroy();
  this.onclose('io server disconnect');
};

/**
 * Called upon forced client/server side disconnections,
 * this method ensures the manager stops tracking us and
 * that reconnections don't get triggered for this.
 *
 * @api private.
 */

Socket.prototype.destroy = function(){
  // clean subscriptions to avoid reconnections
  for (var i = 0; i < this.subs.length; i++) {
    this.subs[i].destroy();
  }

  this.io.destroy(this);
};

/**
 * Disconnects the socket manually.
 *
 * @return {Socket} self
 * @api public
 */

Socket.prototype.close =
Socket.prototype.disconnect = function(){
  if (!this.connected) return this;

  debug('performing disconnect (%s)', this.nsp);
  this.packet({ type: parser.DISCONNECT });

  // remove socket from pool
  this.destroy();

  // fire events
  this.onclose('io client disconnect');
  return this;
};
<commit_msg>Don't readd subs when reconnecting<commit_after>
/**
 * Module dependencies.
 */

var parser = require('socket.io-parser');
var Emitter = require('component-emitter');
var toArray = require('to-array');
var on = require('./on');
var bind = require('component-bind');
var debug = require('debug')('socket.io-client:socket');
var hasBin = require('has-binary-data');
var indexOf = require('indexof');

/**
 * Module exports.
 */

module.exports = exports = Socket;

/**
 * Internal events (blacklisted).
 * These events can't be emitted by the user.
 *
 * @api private
 */

var events = {
  connect: 1,
  connect_error: 1,
  connect_timeout: 1,
  disconnect: 1,
  error: 1,
  reconnect: 1,
  reconnect_attempt: 1,
  reconnect_failed: 1,
  reconnect_error: 1,
  reconnecting: 1
};

/**
 * Shortcut to `Emitter#emit`.
 */

var emit = Emitter.prototype.emit;

/**
 * `Socket` constructor.
 *
 * @api public
 */

function Socket(io, nsp){
  this.io = io;
  this.nsp = nsp;
  this.json = this; // compat
  this.ids = 0;
  this.acks = {};
  this.open();
  this.receiveBuffer = [];
  this.sendBuffer = [];
  this.connected = false;
  this.disconnected = true;
}

/**
 * Mix in `Emitter`.
 */

Emitter(Socket.prototype);

/**
 * Called upon engine `open`.
 *
 * @api private
 */

Socket.prototype.open =
Socket.prototype.connect = function(){
  if (this.connected) return this;

  var io = this.io;
  io.open(); // ensure open

  // Don't readd listeners when manually reconnecting
  if (!this.disconnected) {
    this.subs = [
      on(io, 'open', bind(this, 'onopen')),
      on(io, 'packet', bind(this, 'onpacket')),
      on(io, 'close', bind(this, 'onclose'))
    ];
  }

  if ('open' == this.io.readyState) this.onopen();
  return this;
};

/**
 * Sends a `message` event.
 *
 * @return {Socket} self
 * @api public
 */

Socket.prototype.send = function(){
  var args = toArray(arguments);
  args.unshift('message');
  this.emit.apply(this, args);
  return this;
};

/**
 * Override `emit`.
 * If the event is in `events`, it's emitted normally.
 *
 * @param {String} event name
 * @return {Socket} self
 * @api public
 */

Socket.prototype.emit = function(ev){
  if (events.hasOwnProperty(ev)) {
    emit.apply(this, arguments);
    return this;
  }

  var args = toArray(arguments);
  var parserType = parser.EVENT; // default
  if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary
  var packet = { type: parserType, data: args };

  // event ack callback
  if ('function' == typeof args[args.length - 1]) {
    debug('emitting packet with ack id %d', this.ids);
    this.acks[this.ids] = args.pop();
    packet.id = this.ids++;
  }

  if (this.connected) {
    this.packet(packet);
  } else {
    this.sendBuffer.push(packet);
  }

  return this;
};

/**
 * Sends a packet.
 *
 * @param {Object} packet
 * @api private
 */

Socket.prototype.packet = function(packet){
  packet.nsp = this.nsp;
  this.io.packet(packet);
};

/**
 * "Opens" the socket.
 *
 * @api private
 */

Socket.prototype.onopen = function(){
  debug('transport is open - connecting');

  // write connect packet if necessary
  if ('/' != this.nsp) {
    this.packet({ type: parser.CONNECT });
  }
};

/**
 * Called upon engine `close`.
 *
 * @param {String} reason
 * @api private
 */

Socket.prototype.onclose = function(reason){
  debug('close (%s)', reason);
  this.connected = false;
  this.disconnected = true;
  this.emit('disconnect', reason);
};

/**
 * Called with socket packet.
 *
 * @param {Object} packet
 * @api private
 */

Socket.prototype.onpacket = function(packet){
  if (packet.nsp != this.nsp) return;

  switch (packet.type) {
    case parser.CONNECT:
      this.onconnect();
      break;

    case parser.EVENT:
      this.onevent(packet);
      break;

    case parser.BINARY_EVENT:
      this.onevent(packet);
      break;

    case parser.ACK:
      this.onack(packet);
      break;

    case parser.BINARY_ACK:
      this.onack(packet);
      break;

    case parser.DISCONNECT:
      this.ondisconnect();
      break;

    case parser.ERROR:
      this.emit('error', packet.data);
      break;
  }
};

/**
 * Called upon a server event.
 *
 * @param {Object} packet
 * @api private
 */

Socket.prototype.onevent = function(packet){
  var args = packet.data || [];
  debug('emitting event %j', args);

  if (null != packet.id) {
    debug('attaching ack callback to event');
    args.push(this.ack(packet.id));
  }

  if (this.connected) {
    emit.apply(this, args);
  } else {
    this.receiveBuffer.push(args);
  }
};

/**
 * Produces an ack callback to emit with an event.
 *
 * @api private
 */

Socket.prototype.ack = function(id){
  var self = this;
  var sent = false;
  return function(){
    // prevent double callbacks
    if (sent) return;
    sent = true;
    var args = toArray(arguments);
    debug('sending ack %j', args);

    var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK;
    self.packet({
      type: type,
      id: id,
      data: args
    });
  };
};

/**
 * Called upon a server acknowlegement.
 *
 * @param {Object} packet
 * @api private
 */

Socket.prototype.onack = function(packet){
  debug('calling ack %s with %j', packet.id, packet.data);
  var fn = this.acks[packet.id];
  fn.apply(this, packet.data);
  delete this.acks[packet.id];
};

/**
 * Called upon server connect.
 *
 * @api private
 */

Socket.prototype.onconnect = function(){
  this.connected = true;
  this.disconnected = false;
  this.emit('connect');
  this.emitBuffered();
};

/**
 * Emit buffered events (received and emitted).
 *
 * @api private
 */

Socket.prototype.emitBuffered = function(){
  var i;
  for (i = 0; i < this.receiveBuffer.length; i++) {
    emit.apply(this, this.receiveBuffer[i]);
  }
  this.receiveBuffer = [];

  for (i = 0; i < this.sendBuffer.length; i++) {
    this.packet(this.sendBuffer[i]);
  }
  this.sendBuffer = [];
};

/**
 * Called upon server disconnect.
 *
 * @api private
 */

Socket.prototype.ondisconnect = function(){
  debug('server disconnect (%s)', this.nsp);
  this.destroy();
  this.onclose('io server disconnect');
};

/**
 * Called upon forced client/server side disconnections,
 * this method ensures the manager stops tracking us and
 * that reconnections don't get triggered for this.
 *
 * @api private.
 */

Socket.prototype.destroy = function(){
  // clean subscriptions to avoid reconnections
  for (var i = 0; i < this.subs.length; i++) {
    this.subs[i].destroy();
  }

  this.io.destroy(this);
};

/**
 * Disconnects the socket manually.
 *
 * @return {Socket} self
 * @api public
 */

Socket.prototype.close =
Socket.prototype.disconnect = function(){
  if (!this.connected) return this;

  debug('performing disconnect (%s)', this.nsp);
  this.packet({ type: parser.DISCONNECT });

  // remove socket from pool
  this.destroy();

  // fire events
  this.onclose('io client disconnect');
  return this;
};
<|endoftext|>


<commit_before>/**
 * Created by Matthew on 3/5/2016.
 */

//items is the master list of all items.  The only things that can change this are adding, removing, and loading.
var masterItems = [];
//tags is the master list of all tags.  The only things that can change this are adding, removing, and loading.
var masterTags = [];

/**
 * Item is a constructor for an Item object.  It's the basic organizational element.
 * Item(...) returns an item that does great awesome fun stuff.
 * @param name
 * @param description
 * @param tags
 * @param startTime
 * @param endTime
 * @constructor
 */
function Item(name, description, tags, startTime, endTime) {
    var that = this;
    that.name = name;
    that.description = description;
    that.tags = getTagIndices(tags);
    that.startTime = startTime;
    that.endTime = endTime;

    return that;
}

/**
 * Takes in the input-formatted (string) "tags".  Returns a list of indices into the master tags array,
 * adding new tags to the master tags array if they don't already exist.
 * @param tagsIn
 * @returns {Array}
 */
function getTagIndices(tagsIn) {
    //Get the array of tags instead of an input string.
    var tagsList = tagsIn.split(" ");
    var indices = [];
    //If the tag already exists, push its index.  Otherwise, add it as a new tag, then push its index.
    for(var i = 0; i < tagsList.length; i++) {
        //Edge case when there are no tags right now
        if(masterTags.length == 0) {
            addNewTag(tagsList[i]);
            indices.push(0);
        }
        else { //Normal case.
            for (var j = 0; j < masterTags.length; j++) {
                if (tagsList[i] == masterTags[j]) {
                    //The tag is there, give the index.
                    indices.push(j);
                    break; //no need to stay in the loop, you've already found the index.  Next tag!
                }
                if (j == masterTags.length - 1) {
                    //if this is the last tag in the master list, it's not here.  Add the tag!
                    addNewTag(tagsList[i]);
                    //Now, the function will loop through one more time, since tags is one element bigger.
                    //It will hit the first condition, and push the correct index to the indices array.
                }
            }
        }
    }
    return indices;
}

/**
 * Adds a new tag in the master tag array.
 * @param tagName
 */
function addNewTag(tagName) {
    masterTags.push(tagName);
}

console.log("adding tag1 and tag2 to indices:   " + getTagIndices("tag1 tag2").map(String));
console.log("current tag list:  " + masterTags);
console.log("adding tag2 and tag3 to indices:   " + getTagIndices("tag2 tag3").map(String));
console.log("current tag list:  " + masterTags);
console.log("adding special tag2, tag3, tag1 to indices:   " + getTagIndices("special tag2 tag3 tag1").map(String));
console.log("current tag list:  " + masterTags);

<commit_msg>Added item deletion by index<commit_after>/**
 * Created by Matthew on 3/5/2016.
 */

//items is the master list of all items.  The only things that can change this are adding, removing, and loading.
var masterItems = [];
//tags is the master list of all tags.  The only things that can change this are adding, removing, and loading.
var masterTags = [];

/**
 * Item is a constructor for an Item object.  It's the basic organizational element.
 * Item(...) returns an item that does great awesome fun stuff.
 * @param name
 * @param description
 * @param tags
 * @param startTime
 * @param endTime
 * @constructor
 */
function Item(name, description, tags, startTime, endTime) {
    var that = this;
    that.name = name;
    that.description = description;
    that.tags = getTagIndices(tags);
    that.startTime = startTime;
    that.endTime = endTime;
    return that;
}

/**
 * Deletes an item from the master list according to its index
 * @param itemIndex
 */
function deleteItem(itemIndex) {
    masterItems.splice(itemIndex,1);
}

/**
 * Takes in the input-formatted (string) "tags".  Returns a list of indices into the master tags array,
 * adding new tags to the master tags array if they don't already exist.
 * @param tagsIn
 * @returns {Array}
 */
function getTagIndices(tagsIn) {
    //Get the array of tags instead of an input string.
    var tagsList = tagsIn.split(" ");
    var indices = [];
    //If the tag already exists, push its index.  Otherwise, add it as a new tag, then push its index.
    for(var i = 0; i < tagsList.length; i++) {
        //Edge case when there are no tags right now
        if(masterTags.length == 0) {
            addNewTag(tagsList[i]);
            indices.push(0);
        }
        else { //Normal case.
            for (var j = 0; j < masterTags.length; j++) {
                if (tagsList[i] == masterTags[j]) {
                    //The tag is there, give the index.
                    indices.push(j);
                    break; //no need to stay in the loop, you've already found the index.  Next tag!
                }
                if (j == masterTags.length - 1) {
                    //if this is the last tag in the master list, it's not here.  Add the tag!
                    addNewTag(tagsList[i]);
                    //Now, the function will loop through one more time, since tags is one element bigger.
                    //It will hit the first condition, and push the correct index to the indices array.
                }
            }
        }
    }
    return indices;
}

/**
 * Adds a new tag in the master tag array.
 * @param tagName
 */
function addNewTag(tagName) {
    masterTags.push(tagName);
}

<|endoftext|>