<commit_before>var _ = require('lodash');
var async = require('async-chainable');
var asyncExec = require('async-chainable-exec');
var boxSizing = require('box-sizing');
var colors = require('colors');
var conkieStats = require('conkie-stats');
var ejs = require('ejs');
var electron = require('electron');
var electronDetach = require('electron-detach');
var fs = require('fs');
var fspath = require('path');
var moduleFinder = require('module-finder');
var os = require('os');
var temp = require('temp').track();
var util = require('util');

// Global objects {{{
var app;
var win;
var onBattery; // Whether we are running in low-refresh mode
var tempFile; // File compiled at boot containing main HTML body
// }}}

// Global processes {{{
// Exposed functions designed to work as async callbacks

/**
* Load the theme
* @param function finish The callback to invoke when done
* This process breaks down as follows:
* 1. Read in the main HTML file for the theme
* 2. Figure out all the linked JS / CSS assets
* 3. Read the contents of all assets discovered
* 4. Insert contents inline into HTML stream
* 5. Write a file with all the above
*/
function loadTheme(finish) {
	async()
		.then('content', function(next) {
			// Read in theme file {{{
			fs.readFile(program.theme, 'utf8', next);
			// }}}
		})
		.then('content', function(next) {
			// Scoop all CSS / JS asset links {{{
			// Prepare Async process loader {{{
			var baseContent = this.content;
			var findModules = []; // List of modules we will be needing (fed into module-finder query)
			var modules; // Result of module search
			var scooper = async()
				.then(function(next) {
					if (program.verbose > 2) console.log(colors.blue('[Theme/Preparser]'), 'Find modules', colors.cyan(findModules.map(function(m) { return colors.cyan(m) }).join(', ')));
					moduleFinder({
						local: true,
						global: true,
						cwd: __dirname,
						filter: {
							name: {'$in': findModules}
						},
					}).then(function(res) { modules = res; return next() }, next);
				});
			// }}}

			// Precompile various RegExps {{{
			var linkExtract = /<link.+?href="<%=paths.modules%>\/(.+?)\/(.+?)"/;
			var scriptExtract = /<script.+?src="<%=paths.modules%>\/(.+?)\/(.+?)"/;
			// }}}
			baseContent = baseContent
				// Inline re-write all CSS assets {{{
				.replace(/<link.+?href="<%=paths.modules%>\/.+?".*?>/g, function(block) {
					var bits = linkExtract.exec(block);
					var module = bits[1];
					var cssFile = bits[2];
					var marker = '<!-- CSS FOR [' + module + '/' + cssFile + '] -->';

					findModules.push(module);
					scooper.defer('css-' + cssFile, function(next) {
						var mod = modules.find(function(m) { return m.pkg.name == module });
						if (!mod) return next('Cannot find module "' + module + '" required by CSS pre-load of "' + cssFile + '"');

						var cssPath = fspath.join(fspath.dirname(mod.path), cssFile);

						if (program.verbose > 2) console.log(colors.blue('[Theme/Preparser]'), 'Read CSS asset', colors.cyan(cssPath));
						fs.readFile(cssPath, 'utf8', function(err, content) {
							if (err) return finish(err);
							baseContent = baseContent.replace(marker, marker + '\n' + '<style>' + content + '</style>');
							next();
						});
					});
					return marker;
				})
				// }}}
				// Inline re-write all JS assets {{{
				.replace(/<script.+src="<%=paths.modules%>\/.+?".*?>/g, function(block) {
					var bits = scriptExtract.exec(block);
					var module = bits[1];
					var jsFile = bits[2];
					var marker = '<!-- JS FOR [' + module + '/' + jsFile + '] -->';

					findModules.push(module);
					scooper.defer('js-' + jsFile, function(next) {
						var mod = modules.find(function(m) { return m.pkg.name == module });
						if (!mod) return next('Cannot find module "' + module + '" required by JS pre-load of "' + jsFile + '"');

						var jsPath = fspath.join(fspath.dirname(mod.path), jsFile);

						if (program.verbose > 2) console.log(colors.blue('[Theme/Preparser]'), 'Read JS asset', colors.cyan(jsPath));
						fs.readFile(jsPath, 'utf8', function(err, content) {
							if (err) return finish(err);
							baseContent = baseContent.replace(marker, marker + '\n' + '<script>' + content + '</script>');
							next();
						});
					});
					return marker;
				})
				// }}}

			// Kick all the defered processes off {{{
			scooper
				.await()
				.end(function(err) {
					next(err, baseContent);
				});
			// }}}
			// }}}
		})
		.then(function(next) {
			// Create temp file (which is the EJS compiled template) {{{
			if (tempFile) return next(); // tempFile already setup
			tempFile = temp.path({suffix: '.html'});
			if (program.verbose > 1) console.log(colors.blue('[Conkie]'), 'Setup temp file', colors.cyan(tempFile));
			fs.writeFile(tempFile, ejs.render(this.content, {
				debugMode: program.debug,
				paths: {
					root: 'file://' + __dirname,
					theme: 'file://' + fspath.dirname(program.theme),
					modules: 'file://' + __dirname + '/node_modules',
				},
			}), function(err) {
				if (err) return next(err);
				next(null, tempFile);
			});
			// }}}
		})
		.end(finish);
}
// }}}

// Process command line args {{{
var program = require('commander');

program
	.version(require('./package.json').version)
	.option('-d, --debug', 'Enter debug mode. Show as window and enable dev-tools')
	.option('-v, --verbose', 'Be verbose. Specify multiple times for increasing verbosity', function(i, v) { return v + 1 }, 0)
	.option('-t, --theme [file]', 'Specify main theme HTML file', __dirname + '/themes/default/index.html')
	.option('-b, --background', 'Detach from parent (prevents quitting when parent process dies)')
	.option('--refresh [ms]', 'Time in MS to refresh all system statistics (when on power, default = 1s)', 1000)
	.option('--refresh-battery [ms]', 'Time in MS to refresh system stats (when on battery, default = 10s)', 10000)
	.option('--debug-stats', 'Show stats object being transmitted to front-end')
	.option('--watch', 'Watch the theme directory and reload on any changes')
	.option('--no-color', 'Disable colors')
	.parse(process.env.CONKIE_ARGS ? JSON.parse(process.env.CONKIE_ARGS) : '')
// }}}

// Storage for dynamically updated values {{{
var cpuUsage;
var ifSpeeds = {};
// }}}

async()
	.then(function(next) {
		//  checks {{{
		next();
		// }}}
	})
	.then(function(next) {
		// Setup browser app {{{
		app = electron.app
			.once('window-all-closed', function() {
				if (program.verbose > 2) console.log(colors.blue('[Conkie]'), 'All windows closed');
				if (process.platform != 'darwin') app.quit(); // Kill everything if we're on Darwin
			})
			.once('ready', function() {
				if (program.verbose > 2) console.log(colors.blue('[Conkie]'), 'Electron app ready');
				next();
			})
			.once('error', next);
		// }}}
	})
	.then(function(next) {
		if (!program.background) return next();
		if (electronDetach({requireCmdlineArg: false})) {
			if (program.verbose) console.log(colors.blue('[Conkie]'), 'Detached from parent');
			next();
		} else {
			process.exit(0);
		}
	})
	.then(loadTheme)
	.then(function(next) {
		// Setup page {{{
		// Create the browser window.
		win = new electron.BrowserWindow(
			program.debug
				? {
					width: 1000,
					height: 1000,
					frame: true,
					title: 'Conkie',
					show: false,
				}
				: {
					width: 200,
					height: 1000,
					frame: false,
					resizable: false,
					skipTaskbar: true,
					title: 'Conkie',
					type: 'desktop',
					show: false,
					transparent: true,
					x: 10,
					y: 10,
					center: false,
				}
		);

		win.on('page-title-updated', function(e) {
			// Prevent title changes so we can always find the window
			e.preventDefault();
		})

		win.loadURL('file://' + tempFile);

		win.webContents.once('dom-ready', function() {
			if (program.debug) {
				win.show();
				win.webContents.openDevTools();
			} else {
				win.showInactive();
			}

			return next();
		});
		// }}}
	})
	.parallel([
		// Listen for messages {{{
		function(next) {
			conkieStats
				.on('error', function(err) {
					console.log(colors.blue('[Stats/Error]'), colors.red('ERR', err));
				})
				.on('update', function(stats) {
					if (program.debugStats) console.log(colors.blue('[Stats]'), JSON.stringify(stats, null, '\t'));
					var batStatus = _.get(stats, 'power[0].status');
					if (!onBattery && batStatus == 'discharging') {
						if (program.verbose > 1) console.log(colors.blue('[Stats]'), 'Detected battery mode - adjusting stats poll to', colors.cyan(program.refreshBattery + 'ms'));
						conkieStats.setPollFreq(program.refreshBattery);
						onBattery = true;
					} else if (onBattery && batStatus != 'discharging') {
						if (program.verbose > 1) console.log(colors.blue('[Stats]'), 'Detected powered mode - adjusting stats poll to', colors.cyan(program.refresh+ 'ms'));
						conkieStats.setPollFreq(program.refresh);
						onBattery = false;
					}
					win.webContents.send('updateStats', stats);
				});

			electron.ipcMain
				.on('statsRegister', function() {
					var mods = _.flatten(Array.prototype.slice.call(arguments).slice(1));
					if (program.debug) console.log(colors.blue('[Stats/Debug]'), 'Register stats modules', mods.map(function(m) { return colors.cyan(m) }).join(', '));
					conkieStats.register(mods);
				});

			electron.ipcMain
				.on('statsSettings', function(e, options) {
					if (program.verbose > 2) console.log(colors.blue('[Stats]'), 'Register stats settings', util.inspect(options, {depth: null, colors: true}));
					conkieStats.settings(options);
				});

			electron.ipcMain
				.on('setPosition', function(e, position) {
					if (program.debug) {
						console.log(colors.blue('[Conkie]'), 'Set window position', colors.red('ignored in debug mode'));
						return;
					}

					if (program.verbose > 2) console.log(colors.blue('[Conkie]'), 'Set window position', util.inspect(position, {depth: null, colors: true}));

					var mainScreen = electron.screen.getPrimaryDisplay();
					var calcPosition = boxSizing(position, {
						left: 10,
						top: 10,
						width: '33%',
						height: '33%',
						maxWidth: mainScreen.size.width,
						maxHeight: mainScreen.size.height,
					});

					if (program.verbose > 3) console.log(colors.blue('[Conkie]'), 'Set window position (actual)', util.inspect(calcPosition, {depth: null, colors: true}));

					if (calcPosition) {
						win.setBounds({
							x: calcPosition.left,
							y: calcPosition.top,
							width: calcPosition.width,
							height: calcPosition.height,
						});
					} else {
						if (program.verbose > 2) console.log(colors.blue('[Conkie/setPosition]'), colors.red('ERROR'), 'Invalid window position object', position);
					}
				});

			if (program.debug) {
				conkieStats.on('debug', function(msg) {
					console.log(colors.blue('[Stats/Debug]'), msg);
				})
			}
			next();
		},
		// }}}
		// Apply X window styles {{{
		function(next) {
			if (program.debug) return next();
			async()
				.use(asyncExec)
				.execDefaults({
					log: function(cmd) { console.log(colors.blue('[Conkie/XSetup]'), cmd.cmd + ' ' + cmd.params.join(' ')) },
					out: function(line) {
						line.split('\n').forEach(function(l) {
							console.log(colors.blue('[Conkie/XSetup]'), colors.grey('>'), l);
						});
					},
				})
				.exec([
					'wmctrl',
					'-F',
					'-r',
					'Conkie',
					'-b',
					'add,below',
					'-vvv',
				])
				.exec([
					'wmctrl',
					'-F',
					'-r',
					'Conkie',
					'-b',
					'add,sticky',
					'-vvv',
				])
				.end(next);
		},
		// }}}
		// (Optional) Watch theme directory if `--watch` is specified {{{
		function(next) {
			if (!program.watch) return next();

			var dir = fspath.dirname(program.theme);
			if (program.verbose > 1) console.log(colors.blue('[Conkie/Theme/Watcher]'), 'Watching', colors.cyan(dir));
			fs.watch(dir, {
				persistant: true,
				recursive: true,
			}, function(e, path) {
				if (program.verbose) console.log(colors.blue('[Conkie/Theme/Watcher]'), 'Detected', colors.cyan(e), 'on', colors.cyan(path));
				loadTheme(function(err) {
					if (err) {
						console.log(colors.blue('[Conkie/Theme/Watcher]'), colors.red('ERR'), 'Error while re-loading theme - ' + err.toString());
					} else {
						if (program.verbose) console.log(colors.blue('[Conkie/Theme/Watcher]'), 'Theme reloaded');
						win.webContents.reload();
					}
				});
			});
			next();
		},
		// }}}
	])
	.then(function(next) {
		// Everything done - wait for window to terminate {{{
		win.on('closed', function() {
			next();
		});
		// }}}
	})
	.end(function(err) {
		// Clean up references {{{
		if (app) app.quit();
		win = null; // Remove reference and probably terminate the program
		// }}}

		// Handle exit state {{{
		if (err) {
			console.log(colors.blue('[Conkie]'), colors.red('ERR', err.toString()));
			process.exit(1);
		} else {
			console.log(colors.blue('[Conkie]'), 'Exit');
			process.exit(0);
		}
		// }}}
	});
<commit_msg>Verbosity with X configuration is now limited<commit_after>var _ = require('lodash');
var async = require('async-chainable');
var asyncExec = require('async-chainable-exec');
var boxSizing = require('box-sizing');
var colors = require('colors');
var conkieStats = require('conkie-stats');
var ejs = require('ejs');
var electron = require('electron');
var electronDetach = require('electron-detach');
var fs = require('fs');
var fspath = require('path');
var moduleFinder = require('module-finder');
var os = require('os');
var temp = require('temp').track();
var util = require('util');

// Global objects {{{
var app;
var win;
var onBattery; // Whether we are running in low-refresh mode
var tempFile; // File compiled at boot containing main HTML body
// }}}

// Global processes {{{
// Exposed functions designed to work as async callbacks

/**
* Load the theme
* @param function finish The callback to invoke when done
* This process breaks down as follows:
* 1. Read in the main HTML file for the theme
* 2. Figure out all the linked JS / CSS assets
* 3. Read the contents of all assets discovered
* 4. Insert contents inline into HTML stream
* 5. Write a file with all the above
*/
function loadTheme(finish) {
	async()
		.then('content', function(next) {
			// Read in theme file {{{
			fs.readFile(program.theme, 'utf8', next);
			// }}}
		})
		.then('content', function(next) {
			// Scoop all CSS / JS asset links {{{
			// Prepare Async process loader {{{
			var baseContent = this.content;
			var findModules = []; // List of modules we will be needing (fed into module-finder query)
			var modules; // Result of module search
			var scooper = async()
				.then(function(next) {
					if (program.verbose > 2) console.log(colors.blue('[Theme/Preparser]'), 'Find modules', colors.cyan(findModules.map(function(m) { return colors.cyan(m) }).join(', ')));
					moduleFinder({
						local: true,
						global: true,
						cwd: __dirname,
						filter: {
							name: {'$in': findModules}
						},
					}).then(function(res) { modules = res; return next() }, next);
				});
			// }}}

			// Precompile various RegExps {{{
			var linkExtract = /<link.+?href="<%=paths.modules%>\/(.+?)\/(.+?)"/;
			var scriptExtract = /<script.+?src="<%=paths.modules%>\/(.+?)\/(.+?)"/;
			// }}}
			baseContent = baseContent
				// Inline re-write all CSS assets {{{
				.replace(/<link.+?href="<%=paths.modules%>\/.+?".*?>/g, function(block) {
					var bits = linkExtract.exec(block);
					var module = bits[1];
					var cssFile = bits[2];
					var marker = '<!-- CSS FOR [' + module + '/' + cssFile + '] -->';

					findModules.push(module);
					scooper.defer('css-' + cssFile, function(next) {
						var mod = modules.find(function(m) { return m.pkg.name == module });
						if (!mod) return next('Cannot find module "' + module + '" required by CSS pre-load of "' + cssFile + '"');

						var cssPath = fspath.join(fspath.dirname(mod.path), cssFile);

						if (program.verbose > 2) console.log(colors.blue('[Theme/Preparser]'), 'Read CSS asset', colors.cyan(cssPath));
						fs.readFile(cssPath, 'utf8', function(err, content) {
							if (err) return finish(err);
							baseContent = baseContent.replace(marker, marker + '\n' + '<style>' + content + '</style>');
							next();
						});
					});
					return marker;
				})
				// }}}
				// Inline re-write all JS assets {{{
				.replace(/<script.+src="<%=paths.modules%>\/.+?".*?>/g, function(block) {
					var bits = scriptExtract.exec(block);
					var module = bits[1];
					var jsFile = bits[2];
					var marker = '<!-- JS FOR [' + module + '/' + jsFile + '] -->';

					findModules.push(module);
					scooper.defer('js-' + jsFile, function(next) {
						var mod = modules.find(function(m) { return m.pkg.name == module });
						if (!mod) return next('Cannot find module "' + module + '" required by JS pre-load of "' + jsFile + '"');

						var jsPath = fspath.join(fspath.dirname(mod.path), jsFile);

						if (program.verbose > 2) console.log(colors.blue('[Theme/Preparser]'), 'Read JS asset', colors.cyan(jsPath));
						fs.readFile(jsPath, 'utf8', function(err, content) {
							if (err) return finish(err);
							baseContent = baseContent.replace(marker, marker + '\n' + '<script>' + content + '</script>');
							next();
						});
					});
					return marker;
				})
				// }}}

			// Kick all the defered processes off {{{
			scooper
				.await()
				.end(function(err) {
					next(err, baseContent);
				});
			// }}}
			// }}}
		})
		.then(function(next) {
			// Create temp file (which is the EJS compiled template) {{{
			if (tempFile) return next(); // tempFile already setup
			tempFile = temp.path({suffix: '.html'});
			if (program.verbose > 1) console.log(colors.blue('[Conkie]'), 'Setup temp file', colors.cyan(tempFile));
			fs.writeFile(tempFile, ejs.render(this.content, {
				debugMode: program.debug,
				paths: {
					root: 'file://' + __dirname,
					theme: 'file://' + fspath.dirname(program.theme),
					modules: 'file://' + __dirname + '/node_modules',
				},
			}), function(err) {
				if (err) return next(err);
				next(null, tempFile);
			});
			// }}}
		})
		.end(finish);
}
// }}}

// Process command line args {{{
var program = require('commander');

program
	.version(require('./package.json').version)
	.option('-d, --debug', 'Enter debug mode. Show as window and enable dev-tools')
	.option('-v, --verbose', 'Be verbose. Specify multiple times for increasing verbosity', function(i, v) { return v + 1 }, 0)
	.option('-t, --theme [file]', 'Specify main theme HTML file', __dirname + '/themes/default/index.html')
	.option('-b, --background', 'Detach from parent (prevents quitting when parent process dies)')
	.option('--refresh [ms]', 'Time in MS to refresh all system statistics (when on power, default = 1s)', 1000)
	.option('--refresh-battery [ms]', 'Time in MS to refresh system stats (when on battery, default = 10s)', 10000)
	.option('--debug-stats', 'Show stats object being transmitted to front-end')
	.option('--watch', 'Watch the theme directory and reload on any changes')
	.option('--no-color', 'Disable colors')
	.parse(process.env.CONKIE_ARGS ? JSON.parse(process.env.CONKIE_ARGS) : '')
// }}}

// Storage for dynamically updated values {{{
var cpuUsage;
var ifSpeeds = {};
// }}}

async()
	.then(function(next) {
		//  checks {{{
		next();
		// }}}
	})
	.then(function(next) {
		// Setup browser app {{{
		app = electron.app
			.once('window-all-closed', function() {
				if (program.verbose > 2) console.log(colors.blue('[Conkie]'), 'All windows closed');
				if (process.platform != 'darwin') app.quit(); // Kill everything if we're on Darwin
			})
			.once('ready', function() {
				if (program.verbose > 2) console.log(colors.blue('[Conkie]'), 'Electron app ready');
				next();
			})
			.once('error', next);
		// }}}
	})
	.then(function(next) {
		if (!program.background) return next();
		if (electronDetach({requireCmdlineArg: false})) {
			if (program.verbose) console.log(colors.blue('[Conkie]'), 'Detached from parent');
			next();
		} else {
			process.exit(0);
		}
	})
	.then(loadTheme)
	.then(function(next) {
		// Setup page {{{
		// Create the browser window.
		win = new electron.BrowserWindow(
			program.debug
				? {
					width: 1000,
					height: 1000,
					frame: true,
					title: 'Conkie',
					show: false,
				}
				: {
					width: 200,
					height: 1000,
					frame: false,
					resizable: false,
					skipTaskbar: true,
					title: 'Conkie',
					type: 'desktop',
					show: false,
					transparent: true,
					x: 10,
					y: 10,
					center: false,
				}
		);

		win.on('page-title-updated', function(e) {
			// Prevent title changes so we can always find the window
			e.preventDefault();
		})

		win.loadURL('file://' + tempFile);

		win.webContents.once('dom-ready', function() {
			if (program.debug) {
				win.show();
				win.webContents.openDevTools();
			} else {
				win.showInactive();
			}

			return next();
		});
		// }}}
	})
	.parallel([
		// Listen for messages {{{
		function(next) {
			conkieStats
				.on('error', function(err) {
					console.log(colors.blue('[Stats/Error]'), colors.red('ERR', err));
				})
				.on('update', function(stats) {
					if (program.debugStats) console.log(colors.blue('[Stats]'), JSON.stringify(stats, null, '\t'));
					var batStatus = _.get(stats, 'power[0].status');
					if (!onBattery && batStatus == 'discharging') {
						if (program.verbose > 1) console.log(colors.blue('[Stats]'), 'Detected battery mode - adjusting stats poll to', colors.cyan(program.refreshBattery + 'ms'));
						conkieStats.setPollFreq(program.refreshBattery);
						onBattery = true;
					} else if (onBattery && batStatus != 'discharging') {
						if (program.verbose > 1) console.log(colors.blue('[Stats]'), 'Detected powered mode - adjusting stats poll to', colors.cyan(program.refresh+ 'ms'));
						conkieStats.setPollFreq(program.refresh);
						onBattery = false;
					}
					win.webContents.send('updateStats', stats);
				});

			electron.ipcMain
				.on('statsRegister', function() {
					var mods = _.flatten(Array.prototype.slice.call(arguments).slice(1));
					if (program.debug) console.log(colors.blue('[Stats/Debug]'), 'Register stats modules', mods.map(function(m) { return colors.cyan(m) }).join(', '));
					conkieStats.register(mods);
				});

			electron.ipcMain
				.on('statsSettings', function(e, options) {
					if (program.verbose > 2) console.log(colors.blue('[Stats]'), 'Register stats settings', util.inspect(options, {depth: null, colors: true}));
					conkieStats.settings(options);
				});

			electron.ipcMain
				.on('setPosition', function(e, position) {
					if (program.debug) {
						console.log(colors.blue('[Conkie]'), 'Set window position', colors.red('ignored in debug mode'));
						return;
					}

					if (program.verbose > 2) console.log(colors.blue('[Conkie]'), 'Set window position', util.inspect(position, {depth: null, colors: true}));

					var mainScreen = electron.screen.getPrimaryDisplay();
					var calcPosition = boxSizing(position, {
						left: 10,
						top: 10,
						width: '33%',
						height: '33%',
						maxWidth: mainScreen.size.width,
						maxHeight: mainScreen.size.height,
					});

					if (program.verbose > 3) console.log(colors.blue('[Conkie]'), 'Set window position (actual)', util.inspect(calcPosition, {depth: null, colors: true}));

					if (calcPosition) {
						win.setBounds({
							x: calcPosition.left,
							y: calcPosition.top,
							width: calcPosition.width,
							height: calcPosition.height,
						});
					} else {
						if (program.verbose > 2) console.log(colors.blue('[Conkie/setPosition]'), colors.red('ERROR'), 'Invalid window position object', position);
					}
				});

			if (program.debug) {
				conkieStats.on('debug', function(msg) {
					console.log(colors.blue('[Stats/Debug]'), msg);
				})
			}
			next();
		},
		// }}}
		// Apply X window styles {{{
		function(next) {
			if (program.debug) return next();
			async()
				.use(asyncExec)
				.execDefaults({
					log: function(cmd) {
						if (!program.verbose) return;
						console.log(colors.blue('[Conkie/XSetup]'), cmd.cmd + ' ' + cmd.params.join(' '));
					},
					out: function(line) {
						if (!program.verbose) return;
						line.split('\n').forEach(function(l) {
							console.log(colors.blue('[Conkie/XSetup]'), colors.grey('>'), l);
						});
					},
				})
				.exec([
					'wmctrl',
					'-F',
					'-r',
					'Conkie',
					'-b',
					'add,below',
					'-vvv',
				])
				.exec([
					'wmctrl',
					'-F',
					'-r',
					'Conkie',
					'-b',
					'add,sticky',
					'-vvv',
				])
				.end(next);
		},
		// }}}
		// (Optional) Watch theme directory if `--watch` is specified {{{
		function(next) {
			if (!program.watch) return next();

			var dir = fspath.dirname(program.theme);
			if (program.verbose > 1) console.log(colors.blue('[Conkie/Theme/Watcher]'), 'Watching', colors.cyan(dir));
			fs.watch(dir, {
				persistant: true,
				recursive: true,
			}, function(e, path) {
				if (program.verbose) console.log(colors.blue('[Conkie/Theme/Watcher]'), 'Detected', colors.cyan(e), 'on', colors.cyan(path));
				loadTheme(function(err) {
					if (err) {
						console.log(colors.blue('[Conkie/Theme/Watcher]'), colors.red('ERR'), 'Error while re-loading theme - ' + err.toString());
					} else {
						if (program.verbose) console.log(colors.blue('[Conkie/Theme/Watcher]'), 'Theme reloaded');
						win.webContents.reload();
					}
				});
			});
			next();
		},
		// }}}
	])
	.then(function(next) {
		// Everything done - wait for window to terminate {{{
		win.on('closed', function() {
			next();
		});
		// }}}
	})
	.end(function(err) {
		// Clean up references {{{
		if (app) app.quit();
		win = null; // Remove reference and probably terminate the program
		// }}}

		// Handle exit state {{{
		if (err) {
			console.log(colors.blue('[Conkie]'), colors.red('ERR', err.toString()));
			process.exit(1);
		} else {
			console.log(colors.blue('[Conkie]'), 'Exit');
			process.exit(0);
		}
		// }}}
	});
<|endoftext|>


<commit_before>var request = require('request')
var debug = {
    log: require('debug')('re'),
    http: require('debug')('re.http'),
    metrics: require('debug')('re.metrics')
}
var async = require('async')
var promclient = require('prometheus-client')

process.on('SIGINT', function() {
    debug.log('Received SIGINT - shutting down')
    process.exit(1);
});

var opts = getOptions()
createServer(opts.host, opts.port, opts.listen_port, opts.update_interval)

function getOptions() {
    var opts = {
        // required
        api_access_key:  process.env.API_ACCESS_KEY,
        api_secret_key:  process.env.API_SECRET_KEY,

        // optional
        host:            process.env.HOST || 'localhost',
        port:            process.env.PORT || 8080,
        listen_port:     process.env.LISTEN_PORT || 9010,
        update_interval: process.env.UPDATE_INTERVAL || 5000
    }

    var requiredOpts = [
        'API_ACCESS_KEY',
        'API_SECRET_KEY'
    ]
    requiredOpts.forEach(function(name) {
        if (!opts[name.toLowerCase()]) {
            throw new Error('Missing environment variable for option: ' + name)
            process.exit(1)
        }
    })

    return opts
}

function createServer(host, port, listen_port, update_interval) {
    var client = new promclient()

    var gauge = client.newGauge({
        namespace: 'rancher',
        name: 'environment',
        help: 'Value of 1 if all containers in a stack are active'
    })

    function updateGauge(params, value) {
        gauge.set(params, value)
    }

    function updateMetrics() {
        debug.log('requesting metrics')
        getEnvironmentsState(host, port, function(err, results) {
            if (err) {
                debug.log('failed to get environment state: %s', err.toString())
                throw err
            }
            debug.log('got metric results %o', results)
            Object.keys(results).forEach(function(name) {
                var state = results[name]
                var envName = getSafeName(name)
                var value = (state == 'active') ? 1 : 0
                updateGauge({ name: envName}, value)
            });
        });
    }

    client.listen(listen_port)
    debug.log('listening on %s', listen_port)

    updateMetrics()
    setInterval(updateMetrics, update_interval)
}

function getSafeName(name) {
    return name.replace(/[^a-zA-Z0-9_:]/g, '_')
}

function getEnvironmentsState(host, port, callback) {
    var envIdMap = {}

    async.waterfall([
        function(next) {
            var uri = 'http://' + host + ':' + port + '/v1/projects'
            jsonRequest(uri, function(err, json) {
                if (err) {
                    return next(err)
                }
                var environments = json.data[0].links.environments
                next(null, environments)
            })
        },
        function(environmentsUrl, next) {
            jsonRequest(environmentsUrl, function(err, json) {
                if (err) {
                    return next(err)
                }
                var servicesUrl = json.data.map(function(raw) {
                    return raw.links.services
                });
                json.data.forEach(function(env) {
                    envIdMap[env.id] = env.name
                });
                next(null, servicesUrl)
            });
        },
        function(servicesUrls, next) {
            var tasks = servicesUrls.map(function(servicesUrl) {
                return function(next) {
                    jsonRequest(servicesUrl, next)
                }
            });

            async.parallel(tasks, function(err, results) {
                var data = results.map(function(servicesRaw) {
                    return servicesRaw.data
                });

                next(null, data)
            });
        },
        function(servicesData, next) {
            var services = servicesData.map(function(stackServices) {
                return stackServices.map(function(service) {
                    return {
                        name: service.name,
                        state: service.state,
                        environment: envIdMap[service.environmentId]
                    }
                });
            });

            var flattened = []
            services.forEach(function(service) {
                flattened = flattened.concat(service)
            });

            next(null, flattened)
        },
        function(serviceData, next) {
            var envState = {}
            serviceData.forEach(function(service) {
                if (!envState[service.environment]) {
                    envState[service.environment] = service.state
                } else if (service.state != 'active') {
                    envState[service.environment] = service.state
                }
            });
            next(null, envState)
        }
    ], function(err, results) {
        callback(err, results)
    })
}

function getRequestId() {
    return Math.floor(Math.random() * 100000000000000)
}

function jsonRequest(uri, callback) {
    var requestId = getRequestId()
    debug.http('send request %s: %s', requestId, uri)

    request({
        uri: uri,
        headers: {
            'Accept': 'application/json'
        },
        auth: {
            user: opts.api_access_key,
            pass: opts.api_secret_key,
            sendImmediately: true
        }
    }, function(err, response, body) {
        if (err) {
            debug.http('got error response: %s', err.toString())
            return callback(err)
        }

        debug.http('got response for %s with code %s', requestId, response.statusCode)

        var data;
        try {
            data = JSON.parse(body)
        } catch(e) {
            debug.http('Failed to JSON decode response body')
            var error = new Error('json decode')
            error.response = response
            error.body = body
            return callback(error)
        }

        return callback(null, data)
    })
}
<commit_msg>Add some error handling<commit_after>var request = require('request')
var debug = {
    log: require('debug')('re'),
    http: require('debug')('re.http'),
    metrics: require('debug')('re.metrics')
}
var async = require('async')
var promclient = require('prometheus-client')

process.on('SIGINT', function() {
    debug.log('Received SIGINT - shutting down')
    process.exit(1);
});

var opts = getOptions()
createServer(opts.host, opts.port, opts.listen_port, opts.update_interval)

function getOptions() {
    var opts = {
        // required
        api_access_key:  process.env.API_ACCESS_KEY,
        api_secret_key:  process.env.API_SECRET_KEY,

        // optional
        host:            process.env.HOST || 'localhost',
        port:            process.env.PORT || 8080,
        listen_port:     process.env.LISTEN_PORT || 9010,
        update_interval: process.env.UPDATE_INTERVAL || 5000
    }

    var requiredOpts = [
        'API_ACCESS_KEY',
        'API_SECRET_KEY'
    ]
    requiredOpts.forEach(function(name) {
        if (!opts[name.toLowerCase()]) {
            throw new Error('Missing environment variable for option: ' + name)
            process.exit(1)
        }
    })

    return opts
}

function createServer(host, port, listen_port, update_interval) {
    var client = new promclient()

    var gauge = client.newGauge({
        namespace: 'rancher',
        name: 'environment',
        help: 'Value of 1 if all containers in a stack are active'
    })

    function updateGauge(params, value) {
        gauge.set(params, value)
    }

    function updateMetrics() {
        debug.log('requesting metrics')
        getEnvironmentsState(host, port, function(err, results) {
            if (err) {
                debug.log('failed to get environment state: %s', err.toString())
                throw err
            }
            debug.log('got metric results %o', results)
            Object.keys(results).forEach(function(name) {
                var state = results[name]
                var envName = getSafeName(name)
                var value = (state == 'active') ? 1 : 0
                updateGauge({ name: envName}, value)
            });
        });
    }

    client.listen(listen_port)
    debug.log('listening on %s', listen_port)

    updateMetrics()
    setInterval(updateMetrics, update_interval)
}

function getSafeName(name) {
    return name.replace(/[^a-zA-Z0-9_:]/g, '_')
}

function getEnvironmentsState(host, port, callback) {
    var envIdMap = {}

    async.waterfall([
        function(next) {
            var uri = 'http://' + host + ':' + port + '/v1/projects'
            jsonRequest(uri, function(err, json) {
                if (err) {
                    return next(err)
                }
                if (Array.isArray(json.data) &&
                    json.data[0] &&
                    json.data[0].links &&
                    json.data[0].links.environments
                ) {
                    var environments = json.data[0].links.environments
                    return next(null, environments)
                }
                debug.log('Missing data from API: %o', json)
                return next(new Error('Missing data from API: ' + json.toString()))
            })
        },
        function(environmentsUrl, next) {
            jsonRequest(environmentsUrl, function(err, json) {
                if (err) {
                    return next(err)
                }
                var servicesUrl = json.data.map(function(raw) {
                    return raw.links.services
                });
                json.data.forEach(function(env) {
                    envIdMap[env.id] = env.name
                });
                next(null, servicesUrl)
            });
        },
        function(servicesUrls, next) {
            var tasks = servicesUrls.map(function(servicesUrl) {
                return function(next) {
                    jsonRequest(servicesUrl, next)
                }
            });

            async.parallel(tasks, function(err, results) {
                var data = results.map(function(servicesRaw) {
                    return servicesRaw.data
                });

                next(null, data)
            });
        },
        function(servicesData, next) {
            var services = servicesData.map(function(stackServices) {
                return stackServices.map(function(service) {
                    return {
                        name: service.name,
                        state: service.state,
                        environment: envIdMap[service.environmentId]
                    }
                });
            });

            var flattened = []
            services.forEach(function(service) {
                flattened = flattened.concat(service)
            });

            next(null, flattened)
        },
        function(serviceData, next) {
            var envState = {}
            serviceData.forEach(function(service) {
                if (!envState[service.environment]) {
                    envState[service.environment] = service.state
                } else if (service.state != 'active') {
                    envState[service.environment] = service.state
                }
            });
            next(null, envState)
        }
    ], function(err, results) {
        callback(err, results)
    })
}

function getRequestId() {
    return Math.floor(Math.random() * 100000000000000)
}

function jsonRequest(uri, callback) {
    var requestId = getRequestId()
    debug.http('send request %s: %s', requestId, uri)

    request({
        uri: uri,
        headers: {
            'Accept': 'application/json'
        },
        auth: {
            user: opts.api_access_key,
            pass: opts.api_secret_key,
            sendImmediately: true
        }
    }, function(err, response, body) {
        if (err) {
            debug.http('got error response: %s', err.toString())
            return callback(err)
        }

        debug.http('got response for %s with code %s', requestId, response.statusCode)

        var data;
        try {
            data = JSON.parse(body)
        } catch(e) {
            debug.http('Failed to JSON decode response body')
            var error = new Error('json decode')
            error.response = response
            error.body = body
            return callback(error)
        }

        return callback(null, data)
    })
}
<|endoftext|>


<commit_before>var express = require('express');
var app = express();

app.use(express.static(__dirname + '/public'));

var port = process.env.PORT || 8989;

var server = app.listen(port);

var io = require('socket.io')(server);

var farms = {};

var Farm = function(pw, row, col) {
	this.gold = 100;
	this.farm = [];
	for (var i = 0; i < row; i++) {
		this.farm.push([]);
		for (var j = 0; j < col; j++) {
			this.farm[i].push({row:i, col:j});
		}
	}
	this.inventory = [];
	this.inventory.push({plant: new Plant(), quantity: 3});
	this.password = pw;
};

var Plant = function() {
	this.name = "carrot";
	this.age = 0;
	this.ripetime = 10;
	this.yield = 0;
	this.color = "#FF0000";
	this.shape = 0;
	this.hash = this.name+this.ripetime+this.yield+this.color+this.shape;
};

console.log("Farmland is running on port 8989");
/*
   var farm = new Farm();
   farms["William"] = farm;
   console.log(new Farm());
   plant("William", new Plant(), 3, 3);
   pick("William", 3, 3);
   grow()
   console.log(farms);*/

function plant(owner, crop, row, col) {
	if (farms[owner].farm[row][col].hash === undefined) {
		plant.row = row;
		plant.col = col;
		farms[owner].farm[row][col] = crop;
		return true;
	}
	return false;
}

function pick(owner, row, col) {
	if (farms[owner].farm[row][col].hash != undefined && farms[owner].farm[row][col].age > farms[owner].farm[row][col].ripetime) {
		farms[owner].inventory.push(farms[owner].farm[row][col]);
		farms[owner].farm[row][col] = {row: row, col: col};
		return true;
	}
	return false;
}

function grow() {
	var keys = Object.keys(farms);
	keys.forEach(function(key) {
		if (farms[key]) {
			farms[key].farm.forEach(function(row) {
				row.forEach(function(plant) {
					if (plant.age !== undefined) {
						plant.age++;
					}
				});
			});
		}
	});
}

setInterval(grow, 1000);

io.on('connection', function(socket) {
  var name = "";
  socket.on("createFarm", function(nm, ps, cb) {
    name = nm;
    if (farms[nm] === undefined || farms[nm].password == ps) {
      var farm = new Farm(ps, 7, 7);
      farms[nm] = farm;
      cb(farm);
    } else {
      cb(false);
    }
  });
  socket.on("plant", function(nm, crop, row, col, cb) {
    cb(plant(nm, crop, row, col));
  });
  socket.on("pick", function(nm, row, col, cb) {
    cb(pick(nm, row, col));
  });
});
<commit_msg>merging<commit_after>var express = require('express');
var app = express();

app.use(express.static(__dirname + '/public'));

var port = process.env.PORT || 8989;

var server = app.listen(port);

var io = require('socket.io')(server);

var farms = {};

var Farm = function(pw, row, col) {
	this.gold = 100;
	this.farm = [];
	for (var i = 0; i < row; i++) {
		this.farm.push([]);
		for (var j = 0; j < col; j++) {
			this.farm[i].push({row:i, col:j});
		}
	}
	this.inventory = [];
	this.inventory.push({plant: new Plant(), quantity: 3});
	this.password = pw;
};

var Plant = function() {
	this.name = "carrot";
	this.age = 0;
	this.ripetime = 10;
	this.yield = 0;
	this.color = "#FF0000";
	this.shape = 0;
	this.hash = this.name+this.ripetime+this.yield+this.color+this.shape;
};

console.log("Farmland is running on port 8989");
/*
   var farm = new Farm();
   farms["William"] = farm;
   console.log(new Farm());
   plant("William", new Plant(), 3, 3);
   pick("William", 3, 3);
   grow()
   console.log(farms);*/

function plant(owner, crop, row, col) {
	if (farms[owner].farm[row][col].hash === undefined) {
		plant.row = row;
		plant.col = col;
		farms[owner].farm[row][col] = crop;
		return true;
	}
	return false;
}

function pick(owner, row, col) {
	if (farms[owner].farm[row][col].hash != undefined && farms[owner].farm[row][col].age > farms[owner].farm[row][col].ripetime) {
		farms[owner].inventory.push(farms[owner].farm[row][col]);
		farms[owner].farm[row][col] = {row: row, col: col};
		return true;
	}
	return false;
}

function grow() {
	var keys = Object.keys(farms);
	keys.forEach(function(key) {
		if (farms[key]) {
			farms[key].farm.forEach(function(row) {
				row.forEach(function(plant) {
					if (plant.age !== undefined) {
						plant.age++;
					}
				});
			});
		}
	});
}

setInterval(grow, 1000);

io.on('connection', function(socket) {
  var name = "";
  socket.on("createFarm", function(nm, pw, cb) {
    name = nm;
    if (farms[nm] === undefined || farms[nm].password == pw) {
      var farm = new Farm(pw, 7, 7);
      farms[nm] = farm;
      cb(farm);
    } else {
      cb(false);
    }
  });
  socket.on("plant", function(nm, crop, row, col, cb) {
    cb(plant(nm, crop, row, col));
  });
  socket.on("pick", function(nm, row, col, cb) {
    cb(pick(nm, row, col));
  });
});
<|endoftext|>


<commit_before>var superagent = require( 'superagent' ),
    args = require( 'minimist' )( process.argv.slice( 2 ) ),
    _ = require( 'lodash' ),
    BASE_URL = 'https://api.github.com',
    TOKEN = args.token,
    FILENAME = args.out || 'repos.csv',
    fs = require( 'fs' ),
    queue = [],
    stop = false,
    lastRepo = args.last;

function jsonToCsv( repos ) {
    return _.reduce( repos, function( str, repo ) {
        return str += [
            repo.id,
            repo.name,
            repo.stars,
            repo.watchers,
            repo.language,
            repo.license
        ].join(';') + ';\n';
    }, 'id;name;stars;watchers;language;license;\n');
}

function appendToFile( repos ) {
    var action = fs.existsSync( FILENAME ) ? 'appendFileSync' : 'writeFileSync';
    fs[ action ]( FILENAME, jsonToCsv( repos ) );
}

function save() {
    if ( stop ) {
        return;
    }
    stop = true;
    console.log( 'last repo was', lastRepo );
    appendToFile( queue );
}

function fetchSingle( repo ) {
    var retries = 0;
    console.log( 'Fetching', repo.full_name );
    superagent
        .get( BASE_URL + '/repos/' + repo.full_name )
        .set( 'Authorization', 'Token ' + TOKEN )
        .set( 'Accept', 'application/vnd.github.drax-preview+json' ) // otherwise no license info
        .end( function( err, res ) {
            if ( err ) {
                retries++;
                console.log( 'retrying', repo.full_name );
                return stop || retries > 3 ? false : fetchSingle( repo );
            }
            var full = JSON.parse( res.text );
            console.log( 'Got', full.full_name );
            queue.push({
                id: full.id,
                name: full.full_name,
                license : full.license ? full.license.key : 'none',
                stars: full.stargazers_count,
                watchers: full.watchers_count,
                language: full.language
            });
        });
}

function requestNext() {
    console.log( 'Requesting batch...' );
    if ( stop ) {
        return;
    }
    superagent
        .get( BASE_URL + '/repositories' )
        .query({ since: lastRepo || null })
        .set( 'Authorization', 'Token ' + TOKEN )
        .end( function( err, res ) {
            if ( err ) {
                console.error( err );
                return;
            }
            var repos = JSON.parse( res.text );

            lastRepo = _.last( repos ).id;

            _.forEach( repos, fetchSingle );

            setTimeout( requestNext, 100000 );
        });
}

requestNext();

process.on( 'SIGINT', save );
process.on( 'exit', save );<commit_msg>timeout as parameter<commit_after>var superagent = require( 'superagent' ),
    args = require( 'minimist' )( process.argv.slice( 2 ) ),
    _ = require( 'lodash' ),
    BASE_URL = 'https://api.github.com',
    TOKEN = args.token,
    FILENAME = args.out || 'repos.csv',
    TIMEOUT = args.timeout || 90000,
    fs = require( 'fs' ),
    queue = [],
    stop = false,
    lastRepo = args.last;

function jsonToCsv( repos ) {
    return _.reduce( repos, function( str, repo ) {
        return str += [
            repo.id,
            repo.name,
            repo.stars,
            repo.watchers,
            repo.language,
            repo.license
        ].join(';') + ';\n';
    }, 'id;name;stars;watchers;language;license;\n');
}

function appendToFile( repos ) {
    var action = fs.existsSync( FILENAME ) ? 'appendFileSync' : 'writeFileSync';
    fs[ action ]( FILENAME, jsonToCsv( repos ) );
}

function save() {
    if ( stop ) {
        return;
    }
    stop = true;
    console.log( 'last repo was', lastRepo );
    appendToFile( queue );
}

function fetchSingle( repo ) {
    var retries = 0;
    console.log( 'Fetching', repo.full_name );
    superagent
        .get( BASE_URL + '/repos/' + repo.full_name )
        .set( 'Authorization', 'Token ' + TOKEN )
        .set( 'Accept', 'application/vnd.github.drax-preview+json' ) // otherwise no license info
        .end( function( err, res ) {
            if ( err ) {
                retries++;
                console.log( 'retrying', repo.full_name );
                return stop || retries > 3 ? false : fetchSingle( repo );
            }
            var full = JSON.parse( res.text );
            console.log( 'Got', full.full_name );
            queue.push({
                id: full.id,
                name: full.full_name,
                license : full.license ? full.license.key : 'none',
                stars: full.stargazers_count,
                watchers: full.watchers_count,
                language: full.language
            });
        });
}

function requestNext() {
    console.log( 'Requesting batch...' );
    if ( stop ) {
        return;
    }
    superagent
        .get( BASE_URL + '/repositories' )
        .query({ since: lastRepo || null })
        .set( 'Authorization', 'Token ' + TOKEN )
        .end( function( err, res ) {
            if ( err ) {
                console.error( err );
                return;
            }
            var repos = JSON.parse( res.text );

            lastRepo = _.last( repos ).id;

            _.forEach( repos, fetchSingle );

            setTimeout( requestNext, TIMEOUT );
        });
}

requestNext();

process.on( 'SIGINT', save );
process.on( 'exit', save );<|endoftext|>


<commit_before>'use strict';

var imagePaths = ['bag.jpg', 'banana.jpg', 'bathroom.jpg', 'boots.jpg', 'breakfast.jpg', 'bubblegum.jpg', 'chair.jpg', 'cthulhu.jpg', 'dog-duck.jpg', 'dragon.jpg', 'pen.jpg', 'pet-sweep.jpg', 'scissors.jpg', 'shark.jpg', 'sweep.png', 'tauntaun.jpg', 'unicorn.jpg', 'usb.gif', 'water-can.jpg', 'wine-glass.jpg'];
var images = [];
var currentImageIndices = [0, 1, 2];

// Looping through imagePaths array to create new Image object for each, which pushes to images array
for (var i = 0; i < imagePaths.length; i++) {
  var name = imagePaths[i];
  new Image(null, name);
}
console.log(imagePaths, images);

// Create elements
var imageList = document.getElementById('images');

imageList.addEventListener('click', clickHandler);

drawImage(0);
drawImage(1);
drawImage(2);

// Create clickHandler function for event 'click' which calls on drawImage function
function clickHandler(e) {
  console.log('Target clicked:', event.target);
  var matchPath = event.target.getAttribute('src');
  var arrayOfRandomIndices = randomIndices();
  console.log('Match path:', matchPath);

  for(var j = 0; j < currentImageIndices.length; j++) {
    var currentIndex = currentImageIndices[j];
    var displayedObject = images[currentIndex];
    displayedObject.views += 1;
  }
// Use event target to determine which image was clicked (loop through and compare paths of each)
function findObject() {
  for (var k = 0; k < images.length; k++) {
    var currentImageObject = images[k];
    console.log('Images:', k, currentImageObject);
    if (currentImageObject.path === matchPath) {
      console.log('Found it!', currentImageObject);
      currentImageObject.clicks += 1
    }
  }
}
currentImageIndices = arrayOfRandomIndices;

// Reset imageList;
  imageList.textContent = '';
  drawImage(arrayOfRandomIndices[0]);
  drawImage(arrayOfRandomIndices[1]);
  drawImage(arrayOfRandomIndices[2]);
}

function randomIndices() {
  var firstRandomIndex = Math.floor(Math.random() * images.length);
  var secondRandomIndex = Math.floor(Math.random() * images.length);
  while (firstRandomIndex === secondRandomIndex) {
    secondRandomIndex = Math.floor(Math.random() * images.length);
  }
  var thirdRandomIndex = Math.floor(Math.random() * images.length);
  while (thirdRandomIndex === firstRandomIndex || secondRandomIndex) {
    thirdRandomIndex = Math.floor(Math.random() * images.length);
  }
  return [firstRandomIndex, secondRandomIndex, thirdRandomIndex];
}

function drawImage(index) {
// Use the image path for the src
// (image.path)
    var img = document.createElement('img');
    var li = document.createElement('li');
    var imageList = document.getElementById('images');
    var randomPath = images[index].path;

// Set src
    img.setAttribute('src', 'imgs/' + randomPath);

// Add to DOM
    li.appendChild(img);
    imageList.appendChild(li);
}

function Image(name, path) {
  this.views = 0;
  this.clicks = 0;
  this.name = name;
  this.path = path;

  images.push(this);
}
<commit_msg>fixed event handler function<commit_after>'use strict';

var imagePaths = ['bag.jpg', 'banana.jpg', 'bathroom.jpg', 'boots.jpg', 'breakfast.jpg', 'bubblegum.jpg', 'chair.jpg', 'cthulhu.jpg', 'dog-duck.jpg', 'dragon.jpg', 'pen.jpg', 'pet-sweep.jpg', 'scissors.jpg', 'shark.jpg', 'sweep.png', 'tauntaun.jpg', 'unicorn.jpg', 'usb.gif', 'water-can.jpg', 'wine-glass.jpg'];
var images = [];
var currentImageIndices = [0, 1, 2];

// Looping through imagePaths array to create new Image object for each, which pushes to images array
for (var i = 0; i < imagePaths.length; i++) {
  var name = imagePaths[i];
  new Image(null, name);
}

// Create image li elements
var imageList = document.getElementById('images');

// Add event listener clickHandler for event 'click'
imageList.addEventListener('click', clickHandler);

// Use drawImage function to render an imageList (pass argument of currentImageIndices to ensure three different images are presented)
drawImage(0);
drawImage(1);
drawImage(2);

// Create clickHandler function for event 'click' which calls on drawImage function

function clickHandler(event) {
  console.log('Event target:', event.target);
  var matchPath = event.target.getAttribute('src');
  console.log('Match path:', matchPath);
  var arrayOfRandomIndices = randomIndices();
  console.log('Array of random indices:', arrayOfRandomIndices);

  for(var j = 0; j < currentImageIndices.length; j++) {
    var currentIndex = currentImageIndices[j];
    var displayedObject = images[currentIndex];
    displayedObject.views += 1;
  }

// Use event target to determine which image was clicked (loop through and compare paths of each)
  for (var k = 0; k < images.length; k++) {
    var currentImageObject = images[k];
    console.log('Images:', k, currentImageObject);
    if (currentImageObject.path === matchPath) {
      console.log('Found it!', currentImageObject);
      currentImageObject.clicks += 1
    }
  }
  currentImageIndices = arrayOfRandomIndices;
// Reset imageList;
    imageList.textContent = '';
    drawImage(arrayOfRandomIndices[0]);
    drawImage(arrayOfRandomIndices[1]);
    drawImage(arrayOfRandomIndices[2]);
}

function randomIndices() {
  var firstRandomIndex = Math.floor(Math.random() * images.length);
  var secondRandomIndex = Math.floor(Math.random() * images.length);

  while (firstRandomIndex === secondRandomIndex) {
    secondRandomIndex = Math.floor(Math.random() * images.length);
  }

  var thirdRandomIndex = Math.floor(Math.random() * images.length);

  while (thirdRandomIndex === firstRandomIndex || thirdRandomIndex === secondRandomIndex) {
    thirdRandomIndex = Math.floor(Math.random() * images.length);
  }
  return [firstRandomIndex, secondRandomIndex, thirdRandomIndex];
}

function drawImage(index) {
// Use the image path for the src
// (image.path)
    var img = document.createElement('img');
    var li = document.createElement('li');
    var imageList = document.getElementById('images');
    var randomPath = images[index].path;

// Set src
    img.setAttribute('src', randomPath);

// Add to DOM
    li.appendChild(img);
    imageList.appendChild(li);
}

function Image(name, path) {
  this.views = 0;
  this.clicks = 0;
  this.name = name;
  this.path = 'imgs/' + path;

  images.push(this);
}
<|endoftext|>


<commit_before>var tmi = require('tmi.js');
var request = require('request');
var config = require('./config.js');
var fs = require('fs');
var _ = require('underscore');

var client = new tmi.client(config.tmi);
var admins = config.admins;
var channelsFile = config.channelsFile;
var channels = {};
var priorities = {};
var hostChannel = config.tmi.channels[0] || ("#" + config.tmi.identity.username);
var clientId = config.clientId || ""; // A client ID technically isn't necessary, but it's nice to include to prevent rate-limits: https://github.com/justintv/Twitch-API#rate-limits
var interval = null;

var commands = {
    "!addstreamer": function(params, cb) {
        var channel = params[0];
        var priority = (params[1] && parseInt(params[1]) || 0);
        if(channel) {
            var sharedPriorities = (priorities[priority] || []);
            channel = channel.toLowerCase();
            if(channels[channel]) {
                var oldPriority = channels[channel];
                var oldIndex = priorities[oldPriority].indexOf(channel);
                if(oldIndex > -1) {
                    priorities[oldPriority].splice(oldIndex, 1);
                }
            }
            channels[channel] = priority;
            sharedPriorities.push(channel);
            priorities[priority] = sharedPriorities;
            saveChannels();
            cb("Successfully added \"" + channel + "\" with priority: " + priority);
            return;
        }
        cb("Missing parameter: channel");
        return;
    },
    "!removestreamer": function(params, cb) {
        var channel = params[0];
        if(channel) {
            channel = channel.toLowerCase();
            if(channels[channel] !== undefined) {
                var priority = channels[channel];
                priorities[priority].splice(priorities[priority].indexOf(channel), 1);
                delete channels[channel];
                saveChannels();
                cb("Successfully removed \"" + channel + "\"");
                return;
            }
            cb(channel + " is not on the channel list");
            return;
        }
        cb("Missing parameter: channel");
        return;
    },
    "!listpriorities": function(params, cb) {
        var priority = params[0];
        if(priority) {
            priority = parseInt(priority);
            if(priorities[priority] && priorities[priority].length > 0) {
                cb(priorities[priority].join(", "));
                return;
            }
            cb("No channels listed under priority: " + priority);
            return;
        }
        cb("Missing parameter: priority");
        return;
    },
    "!host": function() {
        hostNewChannel();
    },
    "!rehost": function() {
        hostNewChannel();
    },
    "!status": function(params, cb) {
        checkHosting(function(error, response, body) {
            cb("Currently hosting: " + body);
        });
    },
    "!quit": quit()
};

function quit() {
    client.say(hostChannel, user['display-name'] + " - Exiting application");
    client.disconnect();
    process.exit();
}

function loadChannels(callback) {
    fs.readFile(channelsFile, 'utf8', function(error, data) {
        if (error) {
            throw error;
        }
        callback(JSON.parse(data));
    });
}

function saveChannels() {
    fs.writeFile(channelsFile, JSON.stringify(channels), function(error) {
        if (error) {
            throw error;
        }
    });
}

function checkHosting(callback) {
    var url = 'https://decapi.me/twitch/hosting?channel=' + hostChannel.replace("#", "");
    request(url, callback);
}

function getLiveChannels(callback) {
    var allChannels = Object.keys(channels);
    var liveChannels = [];
    var count = 0;
    var parameters = [
        'limit=100',
        'stream_type=live',
        'client_id=' + clientId
    ];
    _.each(allChannels, function(channel, index) {
        request('https://api.twitch.tv/kraken/streams/' + channel + "?" + parameters.join("&"), function(error, response, body) {
            body = JSON.parse(body);
            if(body.stream) {
                liveChannels.push(channel);
            }

            if(index === (allChannels.length - 1)) {
                callback(liveChannels);
            }
        });
    });
}

function pickPrioritizedChannel(liveChannels, cb) {
    var priorities = {};
    _.each(liveChannels, function(channel, index) {
        var priority = channels[channel];
        if(!priorities[priority]) {
            priorities[priority] = [];
        }
        priorities[priority].push(channel);

        if(index === (liveChannels.length - 1)) {
            var highest = Object.keys(priorities).sort(function(a, b) {return a - b;}).reverse()[0];
            var chan = randArray(priorities[highest]);
            cb(chan);
        }
    });
}

function hostNewChannel() {
    clearInterval(interval);
    getLiveChannels(function(liveChannels) {
        console.log("Fetched live channels: " + (liveChannels.length === 0 ? "No one is live" : liveChannels.join(", ")));
        if(liveChannels.length > 0) {
            pickPrioritizedChannel(liveChannels, function(priorityChannel) {
                client.host(hostChannel, priorityChannel);
                //client.say(hostChannel, "Hosting channel: " + priorityChannel);
                console.log("Hosting channel: " + priorityChannel);
            });
        } else {
            startTimer(300000);
        }
    });
}

function startTimer(delay) {
    console.log("Started timer");
    interval = setInterval(function () {
        hostNewChannel();
        console.log("Cleared timer");
    }, delay);
}

function randArray(array) {
    // Credit: http://stackoverflow.com/a/4550514
    return array[Math.floor(Math.random() * array.length)];
}

loadChannels(function(data) {
    channels = data;
    _.each(channels, function(priority, channel) {
        if(!priorities[priority]) {
            priorities[priority] = [];
        }
        priorities[priority].push(channel);
    });
    console.log("Channels have been loaded.");
    checkHosting(function(error, response, body) {
        console.log("Fetching host info");
        if(body === 'None') {
            // If channel isn't hosting anyone at the time of boot, host new channel.
            console.log("Attempting to host a channel");
            hostNewChannel();
        } else {
            console.log("Already hosting: " + body);
        }
    });
});

client.connect();

if(!config.tmi.channels || config.tmi.channels.length === 0) {
    client.join(hostChannel);
}

client.on('chat', function(channel, user, msg, isSelf) {
    if(!isSelf && admins.indexOf(user.username) !== -1) {
        var split = msg.split(" ");
        var command = split[0].toLowerCase();

        if(commands[command]) {
            var params = split.splice(1);
            commands[command](params, function(message, data) {
                client.say(channel, user["display-name"] + " - " + message + ".");
            });
        }
    }
});

client.on('notice', function(channel, id, message) {
    if(id === 'host_target_went_offline') {
        hostNewChannel();
    }
});

client.on('unhost', function() {
    hostNewChannel();
});

process.on('SIGTERM', function() {
    quit();
});
<commit_msg>Fix broken application quit<commit_after>var tmi = require('tmi.js');
var request = require('request');
var config = require('./config.js');
var fs = require('fs');
var _ = require('underscore');

var client = new tmi.client(config.tmi);
var admins = config.admins;
var channelsFile = config.channelsFile;
var channels = {};
var priorities = {};
var hostChannel = config.tmi.channels[0] || ("#" + config.tmi.identity.username);
var clientId = config.clientId || ""; // A client ID technically isn't necessary, but it's nice to include to prevent rate-limits: https://github.com/justintv/Twitch-API#rate-limits
var interval = null;

var commands = {
    "!addstreamer": function(params, cb) {
        var channel = params[0];
        var priority = (params[1] && parseInt(params[1]) || 0);
        if(channel) {
            var sharedPriorities = (priorities[priority] || []);
            channel = channel.toLowerCase();
            if(channels[channel]) {
                var oldPriority = channels[channel];
                var oldIndex = priorities[oldPriority].indexOf(channel);
                if(oldIndex > -1) {
                    priorities[oldPriority].splice(oldIndex, 1);
                }
            }
            channels[channel] = priority;
            sharedPriorities.push(channel);
            priorities[priority] = sharedPriorities;
            saveChannels();
            cb("Successfully added \"" + channel + "\" with priority: " + priority);
            return;
        }
        cb("Missing parameter: channel");
        return;
    },
    "!removestreamer": function(params, cb) {
        var channel = params[0];
        if(channel) {
            channel = channel.toLowerCase();
            if(channels[channel] !== undefined) {
                var priority = channels[channel];
                priorities[priority].splice(priorities[priority].indexOf(channel), 1);
                delete channels[channel];
                saveChannels();
                cb("Successfully removed \"" + channel + "\"");
                return;
            }
            cb(channel + " is not on the channel list");
            return;
        }
        cb("Missing parameter: channel");
        return;
    },
    "!listpriorities": function(params, cb) {
        var priority = params[0];
        if(priority) {
            priority = parseInt(priority);
            if(priorities[priority] && priorities[priority].length > 0) {
                cb(priorities[priority].join(", "));
                return;
            }
            cb("No channels listed under priority: " + priority);
            return;
        }
        cb("Missing parameter: priority");
        return;
    },
    "!host": function() {
        hostNewChannel();
    },
    "!rehost": function() {
        hostNewChannel();
    },
    "!status": function(params, cb) {
        checkHosting(function(error, response, body) {
            cb("Currently hosting: " + body);
        });
    },
    "!quit": quit()
};

function quit() {
    client.say(hostChannel, "Exiting application");
    client.disconnect();
    process.exit();
}

function loadChannels(callback) {
    fs.readFile(channelsFile, 'utf8', function(error, data) {
        if (error) {
            throw error;
        }
        callback(JSON.parse(data));
    });
}

function saveChannels() {
    fs.writeFile(channelsFile, JSON.stringify(channels), function(error) {
        if (error) {
            throw error;
        }
    });
}

function checkHosting(callback) {
    var url = 'https://decapi.me/twitch/hosting?channel=' + hostChannel.replace("#", "");
    request(url, callback);
}

function getLiveChannels(callback) {
    var allChannels = Object.keys(channels);
    var liveChannels = [];
    var count = 0;
    var parameters = [
        'limit=100',
        'stream_type=live',
        'client_id=' + clientId
    ];
    _.each(allChannels, function(channel, index) {
        request('https://api.twitch.tv/kraken/streams/' + channel + "?" + parameters.join("&"), function(error, response, body) {
            body = JSON.parse(body);
            if(body.stream) {
                liveChannels.push(channel);
            }

            if(index === (allChannels.length - 1)) {
                callback(liveChannels);
            }
        });
    });
}

function pickPrioritizedChannel(liveChannels, cb) {
    var priorities = {};
    _.each(liveChannels, function(channel, index) {
        var priority = channels[channel];
        if(!priorities[priority]) {
            priorities[priority] = [];
        }
        priorities[priority].push(channel);

        if(index === (liveChannels.length - 1)) {
            var highest = Object.keys(priorities).sort(function(a, b) {return a - b;}).reverse()[0];
            var chan = randArray(priorities[highest]);
            cb(chan);
        }
    });
}

function hostNewChannel() {
    clearInterval(interval);
    getLiveChannels(function(liveChannels) {
        console.log("Fetched live channels: " + (liveChannels.length === 0 ? "No one is live" : liveChannels.join(", ")));
        if(liveChannels.length > 0) {
            pickPrioritizedChannel(liveChannels, function(priorityChannel) {
                client.host(hostChannel, priorityChannel);
                //client.say(hostChannel, "Hosting channel: " + priorityChannel);
                console.log("Hosting channel: " + priorityChannel);
            });
        } else {
            startTimer(300000);
        }
    });
}

function startTimer(delay) {
    console.log("Started timer");
    interval = setInterval(function () {
        hostNewChannel();
        console.log("Cleared timer");
    }, delay);
}

function randArray(array) {
    // Credit: http://stackoverflow.com/a/4550514
    return array[Math.floor(Math.random() * array.length)];
}

loadChannels(function(data) {
    channels = data;
    _.each(channels, function(priority, channel) {
        if(!priorities[priority]) {
            priorities[priority] = [];
        }
        priorities[priority].push(channel);
    });
    console.log("Channels have been loaded.");
    checkHosting(function(error, response, body) {
        console.log("Fetching host info");
        if(body === 'None') {
            // If channel isn't hosting anyone at the time of boot, host new channel.
            console.log("Attempting to host a channel");
            hostNewChannel();
        } else {
            console.log("Already hosting: " + body);
        }
    });
});

client.connect();

if(!config.tmi.channels || config.tmi.channels.length === 0) {
    client.join(hostChannel);
}

client.on('chat', function(channel, user, msg, isSelf) {
    if(!isSelf && admins.indexOf(user.username) !== -1) {
        var split = msg.split(" ");
        var command = split[0].toLowerCase();

        if(commands[command]) {
            var params = split.splice(1);
            commands[command](params, function(message, data) {
                client.say(channel, user["display-name"] + " - " + message + ".");
            });
        }
    }
});

client.on('notice', function(channel, id, message) {
    if(id === 'host_target_went_offline') {
        hostNewChannel();
    }
});

client.on('unhost', function() {
    hostNewChannel();
});

process.on('SIGTERM', function() {
    quit();
});
<|endoftext|>


<commit_before>'use strict';

console.log('Launching…');

var meta = require('./package.json');
var express = require('express');
var compression = require('compression');
var bodyParser = require('body-parser');
var exec = require('child_process').exec;
var path = require('path');
var Fs = require('fs');
var Promise = require('promise');
var Uuid = require('node-uuid');

var DocumentDownloader = require('./lib/document-downloader');
var History = require('./lib/history');
var JsonHttpService = require('./lib/json-http-service');
var Publisher = require('./lib/publisher');
var SpecberusWrapper = require('./functions.js').SpecberusWrapper;
var ThirdPartyResourcesChecker = require('./lib/third-party-resources-checker');
var TokenChecker = require('./functions.js').TokenChecker;

// Configuration file
require('./config.js');

// Pseudo-constants:
var STATUS_STARTED = 'started';
var STATUS_ERROR = 'error';
var STATUS_SUCCESS = 'success';

var app = express();
var requests = {};
var argTempLocation = process.argv[2] || global.DEFAULT_TEMP_LOCATION;
var argHttpLocation  = process.argv[3] || global.DEFAULT_HTTP_LOCATION;
var port = process.argv[4] || global.DEFAULT_PORT;
var argResultLocation = process.argv[5] || global.DEFAULT_RESULT_LOCATION;

app.use(compression());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(corsHandler);
app.use(express.static('views'));
app.use(express.static('assets'));

if (process.env.NODE_ENV === 'production') {
  app.set('views', __dirname + '/dist/views');
  app.use(express.static(__dirname + '/dist/assets'));
}
else {
  app.set('views', __dirname + '/views');
  app.use(express.static(__dirname + '/assets'));
}

// Index Page
app.get('/', function (request, response) {
  response.sendFile(__dirname + '/views/index.html');
});

// API methods

app.get('/api/version', function (req, res) {
  res.send(meta.version);
});

app.get('/api/version-specberus', function (req, res) {
  res.send(SpecberusWrapper.version);
});

app.get('/api/status', function (req, res) {
  var id = req.query ? req.query.id : null;
  var file = argResultLocation + path.sep + id + '.json';

  if (id) {
    Fs.exists(file, function (exists) {
      if (exists) res.status(200).sendFile(file);
      else if (requests && requests[id]) {
        res.status(200).json(requests[id]);
      }
      else res.status(404).send('No job found with ID “' + id + '”.');
    });
  }
  else res.status(400).send('Missing required parameter “ID”.');
});

app.post('/api/request', function (req, res) {
  var url = req.body ? req.body.url : null;
  var decision = req.body ? req.body.decision : null;
  var token = req.body ? req.body.token : null;
  var id = Uuid.v4();

  if (!url || !decision || !token) {
    res.status(500).send(
      'Missing required parameters “url”, “decision” and/or “token”.'
    );
  }
  else {
    requests[id] = {
      id: id,
      url: url,
      version: meta.version,
      'version-specberus': SpecberusWrapper.version,
      decision: decision,
      jobs: {},
      history: new History(),
      status: STATUS_STARTED
    };

    orchestrate(requests[id], token).then(function () {
      console.log(
        'Spec at ' + url + ' (decision: ' + decision + ') has FINISHED.'
      );
    }, function () {
      console.log(
        'Spec at ' + url + ' (decision: ' + decision + ') has FAILED.'
      );
    });
    res.status(202).send(id);
  }
});

/**
* Add CORS headers to responses if the client is explicitly allowed.
*
* First, this ensures that the testbed page on the test server, listening on a different port, can GET and POST to Echidna.
* Most importantly, this is necessary to attend publication requests from third parties, eg GitHub.
*/

function corsHandler(req, res, next) {
  if (req && req.headers && req.headers.origin) {
    if (global.ALLOWED_CLIENTS.some(function (regex) {
      return regex.test(req.headers.origin);
    })) {
      res.header('Access-Control-Allow-Origin', req.headers.origin);
      res.header('Access-Control-Allow-Methods', 'GET,POST');
      res.header('Access-Control-Allow-Headers', 'Content-Type');
    }
  }
  next();
}

function trInstaller(source, dest) {
  return new Promise(function (resolve, reject) {
    var cmd = global.TR_INSTALL_CMD + ' ' + source + ' ' + dest;
    exec(cmd, function (err) {
      if (err) reject(err);
      else resolve();
    });
  });
}

function updateTrShortlink(uri) {
  return new Promise(function (resolve, reject) {
    var cmd = global.UPDATE_TR_SHORTLINK_CMD + ' ' + uri;
    exec(cmd, function (err) {
      if (err) reject(err);
      else resolve();
    });
  });
}

function Job() {
  if (typeof this !== 'object') {
    throw new TypeError('Jobs must be constructed via new');
  }

  this.status = '';
  this.errors = [];
}

function dumpJobResult(dest, result) {
  Fs.writeFile(dest, JSON.stringify(result, null, 2) + '\n', function (err) {
    if (err) return console.error(err);
  });
}

function orchestrate(spec, token) {
  spec.jobs['retrieve-resources'] = new Job();
  spec.jobs['specberus'] = new Job();
  spec.jobs['token-checker'] = new Job();
  spec.jobs['third-party-checker'] = new Job();
  spec.jobs['publish'] = new Job();
  spec.jobs['tr-install'] = new Job();
  spec.jobs['update-tr-shortlink'] = new Job();

  var W3C_PREFIX = 'http://www.w3.org';

  var tempLocation = argTempLocation + path.sep + spec.id + path.sep;
  var resultLocation = argResultLocation + path.sep + spec.id + '.json';
  var httpLocation = argHttpLocation + '/' + spec.id + '/Overview.html';

  function runDocumentDownloader(url, tempLocation) {
    return function (state) {
      state.jobs['retrieve-resources'].status = 'pending';

      return DocumentDownloader.fetchAndInstall(url, tempLocation)
        .then(function () {
          state.jobs['retrieve-resources'].status = 'ok';
          state.history = state.history.add('The file has been retrieved.');

          return state;
        }).catch(function (error) {
          state.jobs['retrieve-resources'].status = 'error';
          state.jobs['retrieve-resources'].errors.push(error.toString());
          state.history = state.history.add(
            'The document could not be retrieved.'
          );

          return state;
        });
    };
  }

  function runSpecberus(httpLocation) {
    return function (state) {
      state.jobs['specberus'].status = 'pending';

      return SpecberusWrapper.validate(httpLocation)
        .then(function (report) {
          if (report.errors.isEmpty()) {
            state.jobs['specberus'].status = 'ok';
            state.history = state.history.add('The document passed specberus.');
            state.metadata = report.metadata;

            return state;
          }
          else {
            state.jobs['specberus'].status = 'failure';
            state.jobs['specberus'].errors = report.errors;
            state.history = state.history.add('The document failed Specberus.');

            return state;
          }
        }).catch(function (error) {
          state.jobs['specberus'].status = 'error';
          state.jobs['specberus'].errors.push(error.toString());
          state.history = state.history.add(
            'An error occurred while running Specberus.'
          );

          return state;
        });
    };
  }

  function runTokenChecker(latestVersion, url) {
    return function (state) {
      state.jobs['token-checker'].status = 'pending';

      return TokenChecker.check(latestVersion, token)
        .then(function (authReport) {
          var simpleSource1 = authReport.source.replace(/^https:/i, 'http:');
          var simpleSource2 = authReport.source.replace(/^http:/i, 'https:');
          var matchSource = (
            url.indexOf(simpleSource1) === 0 ||
            url.indexOf(simpleSource2) === 0
          );

          if (authReport.authorized && matchSource) {
            state.jobs['token-checker'].status = 'ok';
            state.history = state.history.add('You are authorized to publish');

            return state;
          }
          else {
            state.jobs['token-checker'].status = 'failure';
            state.jobs['token-checker'].errors.push('Not authorized');
            state.history = state.history.add(
              'You are not authorized to publish'
            );

            return state;
          }
        }).catch(function (error) {
          state.jobs['token-checker'].status = 'error';
          state.jobs['token-checker'].errors.push(error.toString());
          state.history = state.history.add(
            'An error occurred while running the Token Checker.'
          );

          return state;
        });
    };
  }

  function runThirdPartyResourcesChecker(httpLocation) {
    return function (state) {
      state.jobs['third-party-checker'].status = 'pending';

      return ThirdPartyResourcesChecker.check(httpLocation)
        .then(function (extResources) {
          if (extResources.length === 0) {
            state.jobs['third-party-checker'].status = 'ok';
            state.history = state.history.add(
              'The document passed the third party checker.'
            );

            return state;
          }
          else {
            state.history = state.history.add(
              'The document contains non-authorized resources'
            );
            state.jobs['third-party-checker'].status = 'failure';
            state.jobs['third-party-checker'].errors = extResources;

            return state;
          }
        }).catch(function (error) {
          state.jobs['third-party-checker'].status = 'error';
          state.jobs['third-party-checker'].errors.push(error.toString());
          state.history = state.history.add(
            'An error occurred while running the Third Party Resources Checker.'
          );

          return state;
        });
    };
  }

  function runPublisher(metadata) {
    return function (state) {
      state.jobs['publish'].status = 'pending';

      var pubsystemService = new JsonHttpService(
        global.W3C_PUBSYSTEM_URL,
        global.USERNAME,
        global.PASSWORD
      );

      return new Publisher(pubsystemService).publish(metadata)
        .then(function (errors) {
          if (errors.isEmpty()) {
            state.jobs['publish'].status = 'ok';

            return state;
          }
          else {
            state.jobs['publish'].status = 'failure';
            state.jobs['publish'].errors = errors;
            state.history = state.history.add(
              'The document could not be published: ' +
              errors.map(function (error) {
                return error.message;
              })
            );

            return state;
          }
        }).catch(function (error) {
          state.jobs['publish'].status = 'error';
          state.jobs['publish'].errors.push(error.toString());
          state.history = state.history.add(
            'The document could not be published: ' +
            error.message
          );

          return state;
        });
    };
  }

  function runTrInstaller(thisVersion, tempLocation) {
    return function (state) {
      state.jobs['tr-install'].status = 'pending';

      var finalTRpath = thisVersion.replace(W3C_PREFIX, '');

      return trInstaller(tempLocation, finalTRpath)
        .then(function () {
          state.jobs['tr-install'].status = 'ok';

          return state;
        }).catch(function (error) {
          state.jobs['tr-install'].status = 'error';
          state.jobs['tr-install'].errors.push(error.toString());
          state.history = state.history.add(
            'An error occurred while installing the document in TR'
          );

          return state;
        });
    };
  }

  function runShortlink(thisVersion) {
    return function (state) {
      state.jobs['update-tr-shortlink'].status = 'pending';

      return updateTrShortlink(thisVersion)
        .then(function () {
          state.jobs['update-tr-shortlink'].status = 'ok';

          return state;
        }).catch(function (error) {
          state.jobs['update-tr-shortlink'].status = 'error';
          state.jobs['update-tr-shortlink'].errors.push(error.toString());
          state.history = state.history.add(
            'An error occurred while updating the shortlink.'
          );

          return state;
        });
    };
  }

  function finishTasks(thisVersion, resultLocation) {
    return function (state) {
      var cmd =
        global.SENDMAIL + ' SUCCESS ' + global.MAILING_LIST + ' ' + thisVersion;

      exec(cmd, function (err, stdout, stderr) {
        if (err) console.error(stderr);
      });

      state.history = state.history.add(
        'The document has been published at ' +
        '' + thisVersion + '.'
      );
      state.status = STATUS_SUCCESS;

      dumpJobResult(resultLocation, state);

      return state;
    };
  }

  return runDocumentDownloader(spec.url, tempLocation)(spec)
    .then(runSpecberus(httpLocation))
    .then(function (state) {
      return runTokenChecker(
        state.metadata.get('latestVersion'),
        spec.url
      )(state);
    })
    .then(runThirdPartyResourcesChecker(httpLocation))
    .then(function (state) {
      return runPublisher(state.metadata)(state);
    })
    .then(function (state) {
      return runTrInstaller(
        state.metadata.get('thisVersion'),
        tempLocation
      )(state);
    })
    .then(function (state) {
      return runShortlink(state.metadata.get('thisVersion'))(state);
    })
    .then(function (state) {
      return finishTasks(
        state.metadata.get('thisVersion'),
        resultLocation
      )(state);
    })
    .catch(function (err) {
      console.log(err.stack);

      spec.status = STATUS_ERROR;
      var cmd =
        global.SENDMAIL + ' ERROR ' + global.MAILING_LIST + ' ' + spec.url +
        ' \'' + JSON.stringify(spec, null, 2) + '\'';
      exec(cmd, function (err, stdout, stderr) {
        if (err) console.error(stderr);
      });

      spec.history = spec.history.add(
        'A system error occurred during the process.'
      );

      dumpJobResult(resultLocation, spec);
      return Promise.reject(new Error('Orchestrator has failed.'));
    });
}

app.listen(process.env.PORT || port).on('error', function (err) {
  if (err) {
    console.error('Error while trying to launch the server: “' + err + '”.');
  }
});

console.log(
  meta.name +
  ' version ' + meta.version +
  ' running on ' + process.platform +
  ' and listening on port ' + port +
  '. The server time is ' + new Date().toLocaleTimeString() + '.'
);
<commit_msg>Simplify a bit the state machine<commit_after>'use strict';

console.log('Launching…');

var meta = require('./package.json');
var express = require('express');
var compression = require('compression');
var bodyParser = require('body-parser');
var exec = require('child_process').exec;
var path = require('path');
var Fs = require('fs');
var Promise = require('promise');
var Uuid = require('node-uuid');

var DocumentDownloader = require('./lib/document-downloader');
var History = require('./lib/history');
var JsonHttpService = require('./lib/json-http-service');
var Publisher = require('./lib/publisher');
var SpecberusWrapper = require('./functions.js').SpecberusWrapper;
var ThirdPartyResourcesChecker = require('./lib/third-party-resources-checker');
var TokenChecker = require('./functions.js').TokenChecker;

// Configuration file
require('./config.js');

// Pseudo-constants:
var STATUS_STARTED = 'started';
var STATUS_ERROR = 'error';
var STATUS_SUCCESS = 'success';

var app = express();
var requests = {};
var argTempLocation = process.argv[2] || global.DEFAULT_TEMP_LOCATION;
var argHttpLocation  = process.argv[3] || global.DEFAULT_HTTP_LOCATION;
var port = process.argv[4] || global.DEFAULT_PORT;
var argResultLocation = process.argv[5] || global.DEFAULT_RESULT_LOCATION;

app.use(compression());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(corsHandler);
app.use(express.static('views'));
app.use(express.static('assets'));

if (process.env.NODE_ENV === 'production') {
  app.set('views', __dirname + '/dist/views');
  app.use(express.static(__dirname + '/dist/assets'));
}
else {
  app.set('views', __dirname + '/views');
  app.use(express.static(__dirname + '/assets'));
}

// Index Page
app.get('/', function (request, response) {
  response.sendFile(__dirname + '/views/index.html');
});

// API methods

app.get('/api/version', function (req, res) {
  res.send(meta.version);
});

app.get('/api/version-specberus', function (req, res) {
  res.send(SpecberusWrapper.version);
});

app.get('/api/status', function (req, res) {
  var id = req.query ? req.query.id : null;
  var file = argResultLocation + path.sep + id + '.json';

  if (id) {
    Fs.exists(file, function (exists) {
      if (exists) res.status(200).sendFile(file);
      else if (requests && requests[id]) {
        res.status(200).json(requests[id]);
      }
      else res.status(404).send('No job found with ID “' + id + '”.');
    });
  }
  else res.status(400).send('Missing required parameter “ID”.');
});

app.post('/api/request', function (req, res) {
  var url = req.body ? req.body.url : null;
  var decision = req.body ? req.body.decision : null;
  var token = req.body ? req.body.token : null;
  var id = Uuid.v4();

  if (!url || !decision || !token) {
    res.status(500).send(
      'Missing required parameters “url”, “decision” and/or “token”.'
    );
  }
  else {
    requests[id] = {
      id: id,
      url: url,
      version: meta.version,
      'version-specberus': SpecberusWrapper.version,
      decision: decision,
      jobs: {},
      history: new History(),
      status: STATUS_STARTED
    };

    orchestrate(requests[id], token).then(function () {
      console.log(
        'Spec at ' + url + ' (decision: ' + decision + ') has FINISHED.'
      );
    }, function () {
      console.log(
        'Spec at ' + url + ' (decision: ' + decision + ') has FAILED.'
      );
    });
    res.status(202).send(id);
  }
});

/**
* Add CORS headers to responses if the client is explicitly allowed.
*
* First, this ensures that the testbed page on the test server, listening on a different port, can GET and POST to Echidna.
* Most importantly, this is necessary to attend publication requests from third parties, eg GitHub.
*/

function corsHandler(req, res, next) {
  if (req && req.headers && req.headers.origin) {
    if (global.ALLOWED_CLIENTS.some(function (regex) {
      return regex.test(req.headers.origin);
    })) {
      res.header('Access-Control-Allow-Origin', req.headers.origin);
      res.header('Access-Control-Allow-Methods', 'GET,POST');
      res.header('Access-Control-Allow-Headers', 'Content-Type');
    }
  }
  next();
}

function trInstaller(source, dest) {
  return new Promise(function (resolve, reject) {
    var cmd = global.TR_INSTALL_CMD + ' ' + source + ' ' + dest;
    exec(cmd, function (err) {
      if (err) reject(err);
      else resolve();
    });
  });
}

function updateTrShortlink(uri) {
  return new Promise(function (resolve, reject) {
    var cmd = global.UPDATE_TR_SHORTLINK_CMD + ' ' + uri;
    exec(cmd, function (err) {
      if (err) reject(err);
      else resolve();
    });
  });
}

function Job() {
  if (typeof this !== 'object') {
    throw new TypeError('Jobs must be constructed via new');
  }

  this.status = '';
  this.errors = [];
}

function dumpJobResult(dest, result) {
  Fs.writeFile(dest, JSON.stringify(result, null, 2) + '\n', function (err) {
    if (err) return console.error(err);
  });
}

function orchestrate(spec, token) {
  spec.jobs['retrieve-resources'] = new Job();
  spec.jobs['specberus'] = new Job();
  spec.jobs['token-checker'] = new Job();
  spec.jobs['third-party-checker'] = new Job();
  spec.jobs['publish'] = new Job();
  spec.jobs['tr-install'] = new Job();
  spec.jobs['update-tr-shortlink'] = new Job();

  var W3C_PREFIX = 'http://www.w3.org';

  var tempLocation = argTempLocation + path.sep + spec.id + path.sep;
  var resultLocation = argResultLocation + path.sep + spec.id + '.json';
  var httpLocation = argHttpLocation + '/' + spec.id + '/Overview.html';

  function runDocumentDownloader(url, tempLocation) {
    return function (state) {
      state.jobs['retrieve-resources'].status = 'pending';

      return DocumentDownloader.fetchAndInstall(url, tempLocation)
        .then(function () {
          state.jobs['retrieve-resources'].status = 'ok';
          state.history = state.history.add('The file has been retrieved.');

          return state;
        }).catch(function (error) {
          state.jobs['retrieve-resources'].status = 'error';
          state.jobs['retrieve-resources'].errors.push(error.toString());
          state.history = state.history.add(
            'The document could not be retrieved.'
          );

          return state;
        });
    };
  }

  function runSpecberus(httpLocation) {
    return function (state) {
      state.jobs['specberus'].status = 'pending';

      return SpecberusWrapper.validate(httpLocation)
        .then(function (report) {
          if (report.errors.isEmpty()) {
            state.jobs['specberus'].status = 'ok';
            state.history = state.history.add('The document passed specberus.');
            state.metadata = report.metadata;
          }
          else {
            state.jobs['specberus'].status = 'failure';
            state.jobs['specberus'].errors = report.errors;
            state.history = state.history.add('The document failed Specberus.');
          }

          return state;
        }).catch(function (error) {
          state.jobs['specberus'].status = 'error';
          state.jobs['specberus'].errors.push(error.toString());
          state.history = state.history.add(
            'An error occurred while running Specberus.'
          );

          return state;
        });
    };
  }

  function runTokenChecker(latestVersion, url) {
    return function (state) {
      state.jobs['token-checker'].status = 'pending';

      return TokenChecker.check(latestVersion, token)
        .then(function (authReport) {
          var simpleSource1 = authReport.source.replace(/^https:/i, 'http:');
          var simpleSource2 = authReport.source.replace(/^http:/i, 'https:');
          var matchSource = (
            url.indexOf(simpleSource1) === 0 ||
            url.indexOf(simpleSource2) === 0
          );

          if (authReport.authorized && matchSource) {
            state.jobs['token-checker'].status = 'ok';
            state.history = state.history.add('You are authorized to publish');

            return state;
          }
          else {
            state.jobs['token-checker'].status = 'failure';
            state.jobs['token-checker'].errors.push('Not authorized');
            state.history = state.history.add(
              'You are not authorized to publish'
            );

            return state;
          }
        }).catch(function (error) {
          state.jobs['token-checker'].status = 'error';
          state.jobs['token-checker'].errors.push(error.toString());
          state.history = state.history.add(
            'An error occurred while running the Token Checker.'
          );

          return state;
        });
    };
  }

  function runThirdPartyResourcesChecker(httpLocation) {
    return function (state) {
      state.jobs['third-party-checker'].status = 'pending';

      return ThirdPartyResourcesChecker.check(httpLocation)
        .then(function (extResources) {
          if (extResources.length === 0) {
            state.jobs['third-party-checker'].status = 'ok';
            state.history = state.history.add(
              'The document passed the third party checker.'
            );

            return state;
          }
          else {
            state.history = state.history.add(
              'The document contains non-authorized resources'
            );
            state.jobs['third-party-checker'].status = 'failure';
            state.jobs['third-party-checker'].errors = extResources;

            return state;
          }
        }).catch(function (error) {
          state.jobs['third-party-checker'].status = 'error';
          state.jobs['third-party-checker'].errors.push(error.toString());
          state.history = state.history.add(
            'An error occurred while running the Third Party Resources Checker.'
          );

          return state;
        });
    };
  }

  function runPublisher(metadata) {
    return function (state) {
      state.jobs['publish'].status = 'pending';

      var pubsystemService = new JsonHttpService(
        global.W3C_PUBSYSTEM_URL,
        global.USERNAME,
        global.PASSWORD
      );

      return new Publisher(pubsystemService).publish(metadata)
        .then(function (errors) {
          if (errors.isEmpty()) {
            state.jobs['publish'].status = 'ok';

            return state;
          }
          else {
            state.jobs['publish'].status = 'failure';
            state.jobs['publish'].errors = errors;
            state.history = state.history.add(
              'The document could not be published: ' +
              errors.map(function (error) {
                return error.message;
              })
            );

            return state;
          }
        }).catch(function (error) {
          state.jobs['publish'].status = 'error';
          state.jobs['publish'].errors.push(error.toString());
          state.history = state.history.add(
            'The document could not be published: ' +
            error.message
          );

          return state;
        });
    };
  }

  function runTrInstaller(thisVersion, tempLocation) {
    return function (state) {
      state.jobs['tr-install'].status = 'pending';

      var finalTRpath = thisVersion.replace(W3C_PREFIX, '');

      return trInstaller(tempLocation, finalTRpath)
        .then(function () {
          state.jobs['tr-install'].status = 'ok';

          return state;
        }).catch(function (error) {
          state.jobs['tr-install'].status = 'error';
          state.jobs['tr-install'].errors.push(error.toString());
          state.history = state.history.add(
            'An error occurred while installing the document in TR'
          );

          return state;
        });
    };
  }

  function runShortlink(thisVersion) {
    return function (state) {
      state.jobs['update-tr-shortlink'].status = 'pending';

      return updateTrShortlink(thisVersion)
        .then(function () {
          state.jobs['update-tr-shortlink'].status = 'ok';

          return state;
        }).catch(function (error) {
          state.jobs['update-tr-shortlink'].status = 'error';
          state.jobs['update-tr-shortlink'].errors.push(error.toString());
          state.history = state.history.add(
            'An error occurred while updating the shortlink.'
          );

          return state;
        });
    };
  }

  function finishTasks(thisVersion, resultLocation) {
    return function (state) {
      var cmd =
        global.SENDMAIL + ' SUCCESS ' + global.MAILING_LIST + ' ' + thisVersion;

      exec(cmd, function (err, stdout, stderr) {
        if (err) console.error(stderr);
      });

      state.history = state.history.add(
        'The document has been published at ' +
        '' + thisVersion + '.'
      );
      state.status = STATUS_SUCCESS;

      dumpJobResult(resultLocation, state);

      return state;
    };
  }

  return runDocumentDownloader(spec.url, tempLocation)(spec)
    .then(runSpecberus(httpLocation))
    .then(function (state) {
      var metadata = state.metadata;

      return runTokenChecker(metadata.get('latestVersion'), spec.url)(state)
        .then(runThirdPartyResourcesChecker(httpLocation))
        .then(runPublisher(metadata))
        .then(runTrInstaller(metadata.get('thisVersion'), tempLocation))
        .then(runShortlink(metadata.get('thisVersion')))
        .then(finishTasks(metadata.get('thisVersion'), resultLocation))
        .catch(function (err) {
          console.log(err.stack);

          spec.status = STATUS_ERROR;
          var cmd =
            global.SENDMAIL + ' ERROR ' + global.MAILING_LIST + ' ' +
            spec.url + ' \'' + JSON.stringify(spec, null, 2) + '\'';

          exec(cmd, function (err, stdout, stderr) {
            if (err) console.error(stderr);
          });

          spec.history = spec.history.add(
            'A system error occurred during the process.'
          );

          dumpJobResult(resultLocation, spec);
          return Promise.reject(new Error('Orchestrator has failed.'));
        });
    });
}

app.listen(process.env.PORT || port).on('error', function (err) {
  if (err) {
    console.error('Error while trying to launch the server: “' + err + '”.');
  }
});

console.log(
  meta.name +
  ' version ' + meta.version +
  ' running on ' + process.platform +
  ' and listening on port ' + port +
  '. The server time is ' + new Date().toLocaleTimeString() + '.'
);
<|endoftext|>