<commit_before>var Q = require("q"),
	logger = require("../logging").getLogger(__LOGGER__),
	RLS = require("./RequestLocalStorage").getNamespace(),
	PromiseUtil = require("./PromiseUtil");

// There are three data structures defined here that are relevant for page and
// middleware authors:
//
//   - PAGE_MIXIN   : Methods that will be automatically defined on your class.
//   - PAGE_METHODS : Chained methods that may be overridden in your class.
//   - PAGE_HOOKS   : Non-chained methods that may be defined in your class.
//
// These three data structure define the page interface.

// These methods will be available on your page/middleware object.
//
// Accidental definition of a method with a conflicting name directly on your
// class will generate an error.
//
var PAGE_MIXIN = {
	getRequest : makeGetter('request'),
	getConfig  : key => PageConfig.get(key),

	// Methods below here are called for you.
	// You shouldn't need to call them yourself.
	setRequest : makeSetter('request'),
}

// Each item here represents a method that page/middleware objects may override.
//
// The keys here are method names.
//
// The values are tuples containing:
//   - Number of arguments to the method.
//   - Default implementation of the method.
//   - Normalization function applied to method output.
//
// Note that each of these methods also receives an additional argument,
// which is the next implementation of the method in the call chain.
//     - Middleware implementations _should_ call this in most cases.*
//     - Page implementations _may_ call this (it will be the default implementation).
//
// * Consider carefully before deciding not to call `next()` in middleware.
// Other middleware (and the page itself) may exhibit undefined behavior if a
// given method is not called.  Generally, only skip calling `next()` for
// short-circuit responses (e.g. a redirect from `handleRoute`).
//
var PAGE_METHODS = {
	handleRoute        : [1, () => ({code: 200}), Q],
	getTitle           : [0, () => "", Q],
	getScripts         : [0, () => [], standardizeScripts],
	getSystemScripts   : [0, () => [], standardizeScripts],
	getHeadStylesheets : [0, () => [], standardizeStyles],
	getMetaTags        : [0, () => [], standardizeMetaTags],
	getLinkTags        : [0, () => [], standardizeLinkTags],
	getBase            : [0, () => null, Q],
	getBodyClasses     : [0, () => [], Q],
	getElements        : [0, () => [], standardizeElements],
	getResponseData    : [0, () => "", Q],
};

// These are similar to `PAGE_METHODS`, but differ as follows:
//
//   - They are not chained.
//   - They do not have default implementations.
//
// Each page and middleware that implements a page hook will have its hook
// called in turn.  Hooks do not receive a `next()` method, and are not
// responsible for merging return values.
//
// The keys here are method names.
//
// The values are empty placeholder tuples.
//
var PAGE_HOOKS = {
	addConfig      : [], // Define new configuration values.
	setConfig      : [], // Alter existing configuration values.
	handleComplete : [], // Do stuff after the response has been sent.
};

// These are helpers for `PAGE_MIXIN` methods.
//
// Note that getters and setters don't actually modify the page/middleware
// object directly, but rather stash values in request local storage.  Values
// are therefore shared between the page and all middleware.
//
function makeGetter(key){
	return () => (RLS().MixinValues||{})[key];
}

function makeSetter(key){
	return val => {
		(RLS().MixinValues||(RLS().MixinValues={}))[key] = val;
	}
}

// This attaches `PAGE_MIXIN` methods to page/middleware classes.
//
// It does this only _once_, and thereafter short-circuits.
//
function lazyMixinPageUtilMethods(page){
	var proto = Object.getPrototypeOf(page);
	if (proto._haveMixedInPageUtilMethods) return;

	proto._haveMixedInPageUtilMethods = true;

	Object.keys(PAGE_MIXIN).forEach(method => {
		if (proto[method]){
			throw new Error(`PAGE_MIXINS method override: ${
				(proto.constructor||{}).name
			}.${method}`);
		}
		proto[method] = PAGE_MIXIN[method];
	});
}

// These `standardize*` functions show what will happen to the output of your
// page methods.
//
// For middleware authors: Be aware that these standardization functions will
// have been applied to the output of `next()` before you get access to it.
//
// These functions are also exposed via `PageUtil.standardize*`.

/**
 * This method takes in anything returned from a Page.getElements call and
 * returns the elements in a standardized format: an array of EarlyPromises of
 * ReactElements.
 */
function standardizeElements(elements) {

	// The return value could be a single element or an array.
	// First, let's make sure that it's an array.
	// Then, ensure that all elements are EarlyPromises.
	return PageUtil
		.makeArray(elements)
		.map(element => PromiseUtil.early(Q(element)));
}

function standardizeMetaTags(metaTags) {
	return PageUtil.makeArray(metaTags).map(metaTag => Q(metaTag));
}

function standardizeLinkTags(linkTags) {
	return PageUtil.makeArray(linkTags).map(linkTag => Q(linkTag));
}

function standardizeScripts(scripts) {
	return PageUtil.makeArray(scripts).map((script) => {
		if (script.href || script.text) {
			if (!script.type) script.type = "text/javascript";
			return script;
		}

		// if the answer was a string, let's make a script object
		return {href:script, type:"text/javascript"};
	})
}

function standardizeStyles(styles) {
	return PageUtil.makeArray(styles).map((style) => {
		if (style.href || style.text) {
			if (!style.type) style.type = "text/css";
			if (!style.media) style.media = "";

			return style;
		}

		// if the answer was a string, let's make a script object
		return {href:style, type:"text/css", media:""};
	})
}

var PageConfig = (function(){
	var logger = require("../logging").getLogger(__LOGGER__({label: 'PageConfig'}));

	// This gets bound to the outer `PageConfig`.
	//
	// Only `PageConfig.get(key)` is generally useful.
	//
	var PageConfig = {

		get(key) {

			// No access until all `Page.addConfig()` and
			// `Page.getConfig()` methods are complete.
			if (!RLS().PageConfigFinalized){
				throw new Error(`Premature access: "${key}"`);
			}

			// The key _must_ exist.
			if (!_obj().hasOwnProperty(key)){
				throw new Error(`Invalid key: "${key}"`);
			}

			return _obj()[key];
		},

		// Don't call this.  It's called for you.
		initFromPageWithDefaults(page, defaults) {

			// First set all defaults.  Then set all values.
			_setDefaults(defaults);
			page.addConfig().forEach(_setDefaults);
			page.setConfig().forEach(_setValues);

			logger.debug('Final', _obj());

			RLS().PageConfigFinalized = true;
		},
	}

	// Below here are helpers. They are hidden from outside callers.

	var _set = function(isDefault, obj) {

		// Get the current mutable config.
		var config = _obj();

		// Copy input values into it.
		Object.keys(obj||{}).forEach(key => {
			var keyExists = config.hasOwnProperty(key);
			if (isDefault && keyExists){
				// Can't make this fatal, because request
				// forwarding uses a dirty RLS() context.
				logger.warning(`Duplicate PageConfig default: "${key}"`);
			} else if (!isDefault && !keyExists) {
				throw new Error(`Missing PageConfig default: "${key}"`);
			}

			logger.debug(`${isDefault?"Default":"Set"} "${key}" => "${obj[key]}"`);

			config[key] = obj[key];
		});
	};

	var _setDefaults = _set.bind({}, true);
	var _setValues   = _set.bind({}, false);

	var _obj = function(){

		// Return the current mutable config.
		return RLS().PageConfig || (RLS().PageConfig = {});
	}

	return PageConfig;
})();

// This is used to log method calls on the page _chain_.  Method calls on
// individual page/middleware objects are not automatically logged.
var logInvocation = function(name, func){
	return function(){
		logger.debug(`Call ${name}`);
		return func.apply(this, [].slice.call(arguments));
	}
}

var PageUtil = module.exports = {
	PAGE_METHODS,

	standardizeElements,
	standardizeMetaTags,
	standardizeScripts,
	standardizeStyles,

	PageConfig,

	/**
	 * Given an array of page instances, return an object that implements the page interface. This returned object's
	 * methods delegate to the first page in the pages array, passing the second page's implementation as the next
	 * variable (and the second page's implementation gets the third page's next, and so on).
	 */
	createPageChain(pages) {

		// This will be our return value.  It will be a mapping of
		// method names on page objects to chained function calls.
		var pageChain = {};

		pages.forEach(lazyMixinPageUtilMethods);

		Object.keys(PAGE_MIXIN).forEach(method => {
			pageChain[method] = logInvocation(method, PAGE_MIXIN[method]);
		});

		for (var method in PAGE_METHODS){

			if (PAGE_METHODS.hasOwnProperty(method)){

				var [
					argumentCount,
					defaultImpl,
					standardizeReturnValue
				] = PAGE_METHODS[method];

				pageChain[method] = logInvocation(method,
					PageUtil.createObjectFunctionChain(
						pages,
						method,
						argumentCount,
						defaultImpl,
						standardizeReturnValue
					)
				);
			}
		}

		Object.keys(PAGE_HOOKS).forEach(method => {

			// Grab a list of pages that implement this method.
			var implementors = pages.filter(page => page[method]);

			// The resulting function calls each implementor's
			// method in turn and returns an array containing in
			// their return values.
			pageChain[method] = logInvocation(method, function(){

				// The `arguments` object isn't a real array.
				// Pre-es5 `Function.apply()` required a real
				// array.  This `[].slice.call(arguments)`
				// idiom creates a real array with the elements
				// of the `arguments` object.
				//
				// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
				//
				var args = [].slice.call(arguments);

				return implementors.map(
					page => page[method].apply(page, args)
				)
			});
		});

		return pageChain;
	},

	/**
	 * Given an array of objects and a function name, this returns a function that will call functionName on the first
	 * object, adding a next parameter to the end that is a function to call to the next object's implementation of functionName, and so on
	 * down the line. It is a way to make a middleware-like chain of functions, but the functions will be bound to the object in
	 * question from objects.
	 *
	 * The last object in the array will also receive a next parameter, which will call defaultImpl.
	 *
	 * standardizeReturnValue will be called on every return value from functionName so that next will always return the same type. This
	 * is useful when a function can return one of many different types.
	 */
	createObjectFunctionChain(objects, functionName, argumentCount, defaultImpl, standardizeReturnValue) {
		// our first order of business is to get an array of pure functions to chain; this involves finding
		// which objects have implementations for functionName, binding those implementations to the object
		// in question, and standardizing the return values.
		var functionsToChain = [];
		objects.forEach((object) => {
			if (object[functionName]) {
				functionsToChain.push(function() {
					return standardizeReturnValue(object[functionName].apply(object, arguments));
				});
			}
		});
		// the innermost implementation calls the default implementation and standardizes the result.
		functionsToChain.push(function() {
			return standardizeReturnValue(defaultImpl.apply(null, arguments));
		});

		// now we have an array of pure functions to chain.
		return PageUtil.createFunctionChain(functionsToChain, argumentCount);
	},

	createFunctionChain(functions, argumentCount) {
		if (functions.length === 0 ) {
			throw new Error("createFunctionChain cannot be called with zero-length array.");
		}

		// we start from the innermost implementation (i.e. the last in the array) and move outward. nextImpl holds
		// the function that will be used as the next argument for the function before it in the array.
		var nextImpl = functions[functions.length - 1];
		// now let's chain together the other implementations.
		for (var i = functions.length - 2; i >= 0; i--) {
			//
			nextImpl = PageUtil._addArgumentToFunction(functions[i], nextImpl, argumentCount);
		}
		return nextImpl;
	},

	// takes in a function fn and returns a function that calls fn with argument bound at the end of the argument list.
	_addArgumentToFunction(fn, argument, argumentCount) {
		if (argumentCount > 6) {
			throw new Error("_addArgumentToFunction only supports methods with up to 6 arguments");
		}
		return function(a, b, c, d, e, f) {
			var args = [a, b, c, d, e, f];
			args = args.slice(0, argumentCount);
			args.push(argument);
			return fn.apply(null, args);
		}
	},

	makeArray(valueOrArray) {
		if (!Array.isArray(valueOrArray)) {
			return [valueOrArray];
		}
		return valueOrArray;
	}
}<commit_msg>Change some casing<commit_after>var Q = require("q"),
	logger = require("../logging").getLogger(__LOGGER__),
	RLS = require("./RequestLocalStorage").getNamespace(),
	PromiseUtil = require("./PromiseUtil");

// There are three data structures defined here that are relevant for page and
// middleware authors:
//
//   - PAGE_MIXIN   : Methods that will be automatically defined on your class.
//   - PAGE_METHODS : Chained methods that may be overridden in your class.
//   - PAGE_HOOKS   : Non-chained methods that may be defined in your class.
//
// These three data structure define the page interface.

// These methods will be available on your page/middleware object.
//
// Accidental definition of a method with a conflicting name directly on your
// class will generate an error.
//
var PAGE_MIXIN = {
	getRequest : makeGetter('request'),
	getConfig  : key => PageConfig.get(key),

	// Methods below here are called for you.
	// You shouldn't need to call them yourself.
	setRequest : makeSetter('request'),
}

// Each item here represents a method that page/middleware objects may override.
//
// The keys here are method names.
//
// The values are tuples containing:
//   - Number of arguments to the method.
//   - Default implementation of the method.
//   - Normalization function applied to method output.
//
// Note that each of these methods also receives an additional argument,
// which is the next implementation of the method in the call chain.
//     - Middleware implementations _should_ call this in most cases.*
//     - Page implementations _may_ call this (it will be the default implementation).
//
// * Consider carefully before deciding not to call `next()` in middleware.
// Other middleware (and the page itself) may exhibit undefined behavior if a
// given method is not called.  Generally, only skip calling `next()` for
// short-circuit responses (e.g. a redirect from `handleRoute`).
//
var PAGE_METHODS = {
	handleRoute        : [1, () => ({code: 200}), Q],
	getTitle           : [0, () => "", Q],
	getScripts         : [0, () => [], standardizeScripts],
	getSystemScripts   : [0, () => [], standardizeScripts],
	getHeadStylesheets : [0, () => [], standardizeStyles],
	getMetaTags        : [0, () => [], standardizeMetaTags],
	getLinkTags        : [0, () => [], standardizeLinkTags],
	getBase            : [0, () => null, Q],
	getBodyClasses     : [0, () => [], Q],
	getElements        : [0, () => [], standardizeElements],
	getResponseData    : [0, () => "", Q],
};

// These are similar to `PAGE_METHODS`, but differ as follows:
//
//   - They are not chained.
//   - They do not have default implementations.
//
// Each page and middleware that implements a page hook will have its hook
// called in turn.  Hooks do not receive a `next()` method, and are not
// responsible for merging return values.
//
// The keys here are method names.
//
// The values are empty placeholder tuples.
//
var PAGE_HOOKS = {
	addConfig      : [], // Define new configuration values.
	setConfig      : [], // Alter existing configuration values.
	handleComplete : [], // Do stuff after the response has been sent.
};

// These are helpers for `PAGE_MIXIN` methods.
//
// Note that getters and setters don't actually modify the page/middleware
// object directly, but rather stash values in request local storage.  Values
// are therefore shared between the page and all middleware.
//
function makeGetter(key){
	return () => (RLS().mixinValues||{})[key];
}

function makeSetter(key){
	return val => {
		(RLS().mixinValues||(RLS().mixinValues={}))[key] = val;
	}
}

// This attaches `PAGE_MIXIN` methods to page/middleware classes.
//
// It does this only _once_, and thereafter short-circuits.
//
function lazyMixinPageUtilMethods(page){
	var proto = Object.getPrototypeOf(page);
	if (proto._haveMixedInPageUtilMethods) return;

	proto._haveMixedInPageUtilMethods = true;

	Object.keys(PAGE_MIXIN).forEach(method => {
		if (proto[method]){
			throw new Error(`PAGE_MIXINS method override: ${
				(proto.constructor||{}).name
			}.${method}`);
		}
		proto[method] = PAGE_MIXIN[method];
	});
}

// These `standardize*` functions show what will happen to the output of your
// page methods.
//
// For middleware authors: Be aware that these standardization functions will
// have been applied to the output of `next()` before you get access to it.
//
// These functions are also exposed via `PageUtil.standardize*`.

/**
 * This method takes in anything returned from a Page.getElements call and
 * returns the elements in a standardized format: an array of EarlyPromises of
 * ReactElements.
 */
function standardizeElements(elements) {

	// The return value could be a single element or an array.
	// First, let's make sure that it's an array.
	// Then, ensure that all elements are EarlyPromises.
	return PageUtil
		.makeArray(elements)
		.map(element => PromiseUtil.early(Q(element)));
}

function standardizeMetaTags(metaTags) {
	return PageUtil.makeArray(metaTags).map(metaTag => Q(metaTag));
}

function standardizeLinkTags(linkTags) {
	return PageUtil.makeArray(linkTags).map(linkTag => Q(linkTag));
}

function standardizeScripts(scripts) {
	return PageUtil.makeArray(scripts).map((script) => {
		if (script.href || script.text) {
			if (!script.type) script.type = "text/javascript";
			return script;
		}

		// if the answer was a string, let's make a script object
		return {href:script, type:"text/javascript"};
	})
}

function standardizeStyles(styles) {
	return PageUtil.makeArray(styles).map((style) => {
		if (style.href || style.text) {
			if (!style.type) style.type = "text/css";
			if (!style.media) style.media = "";

			return style;
		}

		// if the answer was a string, let's make a script object
		return {href:style, type:"text/css", media:""};
	})
}

var PageConfig = (function(){
	var logger = require("../logging").getLogger(__LOGGER__({label: 'PageConfig'}));

	// This gets bound to the outer `PageConfig`.
	//
	// Only `PageConfig.get(key)` is generally useful.
	//
	var PageConfig = {

		get(key) {

			// No access until all `Page.addConfig()` and
			// `Page.getConfig()` methods are complete.
			if (!RLS().pageConfigFinalized){
				throw new Error(`Premature access: "${key}"`);
			}

			// The key _must_ exist.
			if (!_obj().hasOwnProperty(key)){
				throw new Error(`Invalid key: "${key}"`);
			}

			return _obj()[key];
		},

		// Don't call this.  It's called for you.
		initFromPageWithDefaults(page, defaults) {

			// First set all defaults.  Then set all values.
			_setDefaults(defaults);
			page.addConfig().forEach(_setDefaults);
			page.setConfig().forEach(_setValues);

			logger.debug('Final', _obj());

			RLS().pageConfigFinalized = true;
		},
	}

	// Below here are helpers. They are hidden from outside callers.

	var _set = function(isDefault, obj) {

		// Get the current mutable config.
		var config = _obj();

		// Copy input values into it.
		Object.keys(obj||{}).forEach(key => {
			var keyExists = config.hasOwnProperty(key);
			if (isDefault && keyExists){
				// Can't make this fatal, because request
				// forwarding uses a dirty RLS() context.
				logger.warning(`Duplicate PageConfig default: "${key}"`);
			} else if (!isDefault && !keyExists) {
				throw new Error(`Missing PageConfig default: "${key}"`);
			}

			logger.debug(`${isDefault?"Default":"Set"} "${key}" => "${obj[key]}"`);

			config[key] = obj[key];
		});
	};

	var _setDefaults = _set.bind({}, true);
	var _setValues   = _set.bind({}, false);

	var _obj = function(){

		// Return the current mutable config.
		return RLS().pageConfig || (RLS().pageConfig = {});
	}

	return PageConfig;
})();

// This is used to log method calls on the page _chain_.  Method calls on
// individual page/middleware objects are not automatically logged.
var logInvocation = function(name, func){
	return function(){
		logger.debug(`Call ${name}`);
		return func.apply(this, [].slice.call(arguments));
	}
}

var PageUtil = module.exports = {
	PAGE_METHODS,

	standardizeElements,
	standardizeMetaTags,
	standardizeScripts,
	standardizeStyles,

	PageConfig,

	/**
	 * Given an array of page instances, return an object that implements the page interface. This returned object's
	 * methods delegate to the first page in the pages array, passing the second page's implementation as the next
	 * variable (and the second page's implementation gets the third page's next, and so on).
	 */
	createPageChain(pages) {

		// This will be our return value.  It will be a mapping of
		// method names on page objects to chained function calls.
		var pageChain = {};

		pages.forEach(lazyMixinPageUtilMethods);

		Object.keys(PAGE_MIXIN).forEach(method => {
			pageChain[method] = logInvocation(method, PAGE_MIXIN[method]);
		});

		for (var method in PAGE_METHODS){

			if (PAGE_METHODS.hasOwnProperty(method)){

				var [
					argumentCount,
					defaultImpl,
					standardizeReturnValue
				] = PAGE_METHODS[method];

				pageChain[method] = logInvocation(method,
					PageUtil.createObjectFunctionChain(
						pages,
						method,
						argumentCount,
						defaultImpl,
						standardizeReturnValue
					)
				);
			}
		}

		Object.keys(PAGE_HOOKS).forEach(method => {

			// Grab a list of pages that implement this method.
			var implementors = pages.filter(page => page[method]);

			// The resulting function calls each implementor's
			// method in turn and returns an array containing in
			// their return values.
			pageChain[method] = logInvocation(method, function(){

				// The `arguments` object isn't a real array.
				// Pre-es5 `Function.apply()` required a real
				// array.  This `[].slice.call(arguments)`
				// idiom creates a real array with the elements
				// of the `arguments` object.
				//
				// See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
				//
				var args = [].slice.call(arguments);

				return implementors.map(
					page => page[method].apply(page, args)
				)
			});
		});

		return pageChain;
	},

	/**
	 * Given an array of objects and a function name, this returns a function that will call functionName on the first
	 * object, adding a next parameter to the end that is a function to call to the next object's implementation of functionName, and so on
	 * down the line. It is a way to make a middleware-like chain of functions, but the functions will be bound to the object in
	 * question from objects.
	 *
	 * The last object in the array will also receive a next parameter, which will call defaultImpl.
	 *
	 * standardizeReturnValue will be called on every return value from functionName so that next will always return the same type. This
	 * is useful when a function can return one of many different types.
	 */
	createObjectFunctionChain(objects, functionName, argumentCount, defaultImpl, standardizeReturnValue) {
		// our first order of business is to get an array of pure functions to chain; this involves finding
		// which objects have implementations for functionName, binding those implementations to the object
		// in question, and standardizing the return values.
		var functionsToChain = [];
		objects.forEach((object) => {
			if (object[functionName]) {
				functionsToChain.push(function() {
					return standardizeReturnValue(object[functionName].apply(object, arguments));
				});
			}
		});
		// the innermost implementation calls the default implementation and standardizes the result.
		functionsToChain.push(function() {
			return standardizeReturnValue(defaultImpl.apply(null, arguments));
		});

		// now we have an array of pure functions to chain.
		return PageUtil.createFunctionChain(functionsToChain, argumentCount);
	},

	createFunctionChain(functions, argumentCount) {
		if (functions.length === 0 ) {
			throw new Error("createFunctionChain cannot be called with zero-length array.");
		}

		// we start from the innermost implementation (i.e. the last in the array) and move outward. nextImpl holds
		// the function that will be used as the next argument for the function before it in the array.
		var nextImpl = functions[functions.length - 1];
		// now let's chain together the other implementations.
		for (var i = functions.length - 2; i >= 0; i--) {
			//
			nextImpl = PageUtil._addArgumentToFunction(functions[i], nextImpl, argumentCount);
		}
		return nextImpl;
	},

	// takes in a function fn and returns a function that calls fn with argument bound at the end of the argument list.
	_addArgumentToFunction(fn, argument, argumentCount) {
		if (argumentCount > 6) {
			throw new Error("_addArgumentToFunction only supports methods with up to 6 arguments");
		}
		return function(a, b, c, d, e, f) {
			var args = [a, b, c, d, e, f];
			args = args.slice(0, argumentCount);
			args.push(argument);
			return fn.apply(null, args);
		}
	},

	makeArray(valueOrArray) {
		if (!Array.isArray(valueOrArray)) {
			return [valueOrArray];
		}
		return valueOrArray;
	}
}<|endoftext|>


<commit_before>import 'colors';
import yargs from 'yargs';
import Promise from 'bluebird';
import Account from '../models/account';
import database from '../db/database';
import fs from 'fs';
import path from 'path';
import { DataSource } from 'fulcrum-core';
import LocalDatabaseDataSource from '../local-database-data-source';
import { Postgres } from 'minidb';

Promise.longStackTraces();

const config = JSON.parse(fs.readFileSync(path.join('data', 'config.json')).toString());

export default class Command {
  async setup() {
    this._db = await database(this.config);
  }

  async destroy() {
    await this._db.close();

    Postgres.shutdown();
  }

  get db() {
    return this._db;
  }

  get args() {
    return yargs.argv;
  }

  get config() {
    return config;
  }

  async fetchAccount(name) {
    const where = {};

    if (name) {
      where.organization_name = name;
    }

    const accounts = await Account.findAll(this.db, where);

    return accounts;
  }

  async createDataSource(account) {
    let dataSource = new DataSource();

    const localDatabase = new LocalDatabaseDataSource(account);

    dataSource.add(localDatabase);

    await localDatabase.load(this.db);

    return dataSource;
  }

  async start() {
    try {
      await this.setup();
      await this.run();
      await this.destroy();
    } catch (err) {
      console.error(err.stack);
      await this.destroy();
      throw err;
    }
  }
}
<commit_msg>Always be verbose for now<commit_after>import 'colors';
import yargs from 'yargs';
import Promise from 'bluebird';
import Account from '../models/account';
import database from '../db/database';
import fs from 'fs';
import path from 'path';
import { DataSource } from 'fulcrum-core';
import LocalDatabaseDataSource from '../local-database-data-source';
import { Postgres } from 'minidb';

Promise.longStackTraces();

const config = JSON.parse(fs.readFileSync(path.join('data', 'config.json')).toString());

export default class Command {
  async setup() {
    this._db = await database(this.config);
  }

  async destroy() {
    await this._db.close();

    Postgres.shutdown();
  }

  get db() {
    return this._db;
  }

  get args() {
    return yargs.argv;
  }

  get config() {
    return config;
  }

  async fetchAccount(name) {
    const where = {};

    if (name) {
      where.organization_name = name;
    }

    const accounts = await Account.findAll(this.db, where);

    return accounts;
  }

  async createDataSource(account) {
    let dataSource = new DataSource();

    const localDatabase = new LocalDatabaseDataSource(account);

    dataSource.add(localDatabase);

    await localDatabase.load(this.db);

    return dataSource;
  }

  async start() {
    try {
      await this.setup();
      await this.run();
      await this.destroy();
    } catch (err) {
      console.error(err.stack);
      // if (this.args.verbose) {
      //   console.error(err.stack);
      // } else {
      //   console.error(err.message);
      // }

      await this.destroy();
    }
  }
}
<|endoftext|>


<commit_before>(function() {
  $.fn.easyAudio = function(options) {

    return this.each(function() {
      var opts = $.extend({}, $.fn.easyAudio.defaults, options);
      opts.$elem = $(this);

      var configured = $.fn.easyAudio.configure(opts);

      if ( ! configured) {
        alert("Your browser doesn't support audio");
        return false;
      }

      $.fn.easyAudio.bindEvents(opts);
    });

  };

  $.fn.easyAudio.defaults = {
    event: 'click'
  };

  $.fn.easyAudio.bindEvents = function(opts) {
    var ev = opts.event + '.easyaudio';
    opts.$elem.bind(ev, function() {
      var audio = $.fn.easyAudio.addAudio(opts);
      audio.play();

      opts.$elem.unbind(ev);
      $.fn.easyAudio.bindEvents(opts);

      if (opts.onPlay) {
        opts.onPlay();
      }
    });
  }

  $.fn.easyAudio.addAudio = function(opts) {
    var $audio = $('<audio src="' + $.fn.easyAudio.getSound(opts) + '" preload="auto"></audio');
    $audio.bind('ended', function() {
      $(this).remove();
      if (opts.onEnd) {
        opts.onEnd();
      }
    });
    return $audio[0]
  }

  $.fn.easyAudio.getSound = function(opts) {
    var sounds = opts.sound;
    if (Object.prototype.toString.call(sounds) === "[object Array]") {
      return sounds[Math.floor(Math.random() * sounds.length)] + '.' + opts.extension;
    } else {
      return sounds + '.' + opts.extension;
    }
  },

  $.fn.easyAudio.configure = function(opts) {
    var audio = document.createElement('audio');
    if (!!!audio.canPlayType) return false;

    var types = { 'audio/mpeg' : 'mp3', 'audio/wav' : 'wav', 'audio/ogg' : 'ogg' };
    var keys = Object.keys(types);

    for (var i = 0; i < keys.length; i++) {
      if (!!audio.canPlayType(keys[i])) {
        opts.extension = types[keys[i]];
        $.fn.easyAudio.preloadAudio(opts);
        return true;
      }
    }

    return false;
  };

  $.fn.easyAudio.preloadAudio = function(opts) {
    var audio = new Audio();
    audio.preload = "preload";
    audio.src = $.fn.easyAudio.getSound(opts);
  }

}(jQuery));
<commit_msg>semicolons<commit_after>(function() {
  $.fn.easyAudio = function(options) {

    return this.each(function() {
      var opts = $.extend({}, $.fn.easyAudio.defaults, options);
      opts.$elem = $(this);

      var configured = $.fn.easyAudio.configure(opts);

      if ( ! configured) {
        window.alert("Your browser doesn't support audio");
        return false;
      }

      $.fn.easyAudio.bindEvents(opts);
    });

  };

  $.fn.easyAudio.defaults = {
    event: 'click'
  };

  $.fn.easyAudio.bindEvents = function(opts) {
    var ev = opts.event + '.easyaudio';
    opts.$elem.bind(ev, function() {
      var audio = $.fn.easyAudio.addAudio(opts);
      audio.play();

      opts.$elem.unbind(ev);
      $.fn.easyAudio.bindEvents(opts);

      if (opts.onPlay) {
        opts.onPlay();
      }
    });
  };

  $.fn.easyAudio.addAudio = function(opts) {
    var $audio = $('<audio src="' + $.fn.easyAudio.getSound(opts) + '" preload="auto"></audio');
    $audio.bind('ended', function() {
      $(this).remove();
      if (opts.onEnd) {
        opts.onEnd();
      }
    });
    return $audio[0];
  };

  $.fn.easyAudio.getSound = function(opts) {
    var sounds = opts.sound;
    if (Object.prototype.toString.call(sounds) === "[object Array]") {
      return sounds[Math.floor(Math.random() * sounds.length)] + '.' + opts.extension;
    } else {
      return sounds + '.' + opts.extension;
    }
  },

  $.fn.easyAudio.configure = function(opts) {
    var audio = document.createElement('audio');
    if (!!!audio.canPlayType) {
      return false;
    }

    var types = { 'audio/mpeg' : 'mp3', 'audio/wav' : 'wav', 'audio/ogg' : 'ogg' };
    var keys = Object.keys(types);

    for (var i = 0; i < keys.length; i++) {
      if (!!audio.canPlayType(keys[i])) {
        opts.extension = types[keys[i]];
        $.fn.easyAudio.preloadAudio(opts);
        return true;
      }
    }

    return false;
  };

  $.fn.easyAudio.preloadAudio = function(opts) {
    var audio = new Audio();
    audio.preload = "preload";
    audio.src = $.fn.easyAudio.getSound(opts);
  }

}(jQuery));
<|endoftext|>


<commit_before>//todo write angular scripts for developer page.

var developerKingdomModule = angular.module('app', []).
	controller("forumCtrl", function forumCtrl($scope, $window, $http){

		$scope.forum = {
			threads: [{subject: "blah blah blah", id: "1",  posts: []},
				{subject: "how to add your own functions", posts: []}
			]
		};

		var getThreadIndexFromID = function(id){
			for(var i = 0; i < $scope.forum.threads.length; i++){
				if($scope.forum.threads[i].id == id){
					return i;
				}
			}
		}

		$scope.getPosts = function(){
			var additionalPosts = [];
			$http({method: "GET", url: "http://localhost:3000/posts"})
				.success(function(data, status){
					additionalPosts = data;
					console.log("Additional posts: " + additionalPosts);

				for(var i = 0; i < additionalPosts.length; i++){
					var relevantThreadIndex = getThreadIndexFromID(additionalPosts[i].threadID);
					$scope.forum.threads[relevantThreadIndex].posts.push(additionalPosts[i]);
				}
			})
			.error(function(data, status){
				additionalPosts = data || "Request failed";
				console.log(additionalPosts);
			});
		};

		$scope.fetch = function(){
			var additionalThreads = [];

			$http({method: "GET", url: "http://localhost:3000/threads"})
			.success(function(data, status){
				additionalThreads = data;
				console.log("Additional threads: " + additionalThreads)
				for(var i = 0; i < additionalThreads; i++){
					$scope.forum.threads.push(additionalThreads[i]);
				}
			}).error(function(data, status){
				console.log("Threads Fail");
			});
		};

		$scope.fetch();
		$scope.getPosts();

		$scope.currentThread = {};
		$scope.currentThreadOn = false;

		$scope.setThread = function(thread){
			$scope.currentThread = thread;
			$scope.currentThreadOn = true;
		}

		$scope.backToForum = function(){
			$scope.currentThreadOn = false;
		}

		$scope.addPost = function(thread){
			if($scope.currentUser == "" || $scope.currentUser == null){
				$scope.currentUser = "Guest";
			}

			var postObject = {user: $scope.currentUser, message: $scope.newPost, time: new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')};

			$scope.currentThread.posts.push(postObject);

			var stringifiedPost = JSON.stringify(postObject);
			$http({method: "POST", url: "http://localhost:3000/posts", data: stringifiedPost})

			$scope.newPost = "";
		}

		$scope.addThread = function(){
			if($scope.currentUser == "" || $scope.currentUser == null){
				$scope.currentUser = "Guest";
			}
			$scope.forum.threads.push({
				subject: $scope.newSubject,
				posts: [{user: $scope.currentUser, message: $scope.newPost, time: new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')}]
			});

			$scope.newSubject = "";
			$scope.newPost = "";
		}

		$scope.currentUser = "";
		$scope.newPost = "";
		$scope.newSubject = "";
		$scope.predicate = '';
	});
<commit_msg>Update forum.js<commit_after>//todo write angular scripts for developer page.

var developerKingdomModule = angular.module('app', []).
	controller("forumCtrl", function forumCtrl($scope, $window, $http){

		$scope.forum = {
			threads: [{subject: "blah blah blah", id: "1",  posts: []},
				{subject: "how to add your own functions", posts: []}
			]
		};

		var getThreadIndexFromID = function(id){
			for(var i = 0; i < $scope.forum.threads.length; i++){
				if($scope.forum.threads[i].id == id){
					return i;
				}
			}
		}

		$scope.getPosts = function(){
			var additionalPosts = [];
			$http({method: "GET", url: "http://localhost:3000/posts"})
				.success(function(data, status){
					additionalPosts = data;
					console.log("Additional posts: " + additionalPosts);

				for(var i = 0; i < additionalPosts.length; i++){
					var relevantThreadIndex = getThreadIndexFromID(additionalPosts[i].threadID);
					$scope.forum.threads[relevantThreadIndex].posts.push(additionalPosts[i]);
				}
			})
			.error(function(data, status){
				additionalPosts = data || "Request failed";
				console.log(additionalPosts);
			});
		};

		$scope.fetch = function(){
			var additionalThreads = [];

			$http({method: "GET", url: "http://localhost:3000/threads"})
			.success(function(data, status){
				additionalThreads = data;
				console.log("Additional threads: " + additionalThreads)
				for(var i = 0; i < additionalThreads.length; i++){
					$scope.forum.threads.push(additionalThreads[i]);
				}
			}).error(function(data, status){
				console.log("Threads Fail");
			});
		};

		$scope.fetch();
		$scope.getPosts();

		$scope.currentThread = {};
		$scope.currentThreadOn = false;

		$scope.setThread = function(thread){
			$scope.currentThread = thread;
			$scope.currentThreadOn = true;
		}

		$scope.backToForum = function(){
			$scope.currentThreadOn = false;
		}

		$scope.addPost = function(thread){
			if($scope.currentUser == "" || $scope.currentUser == null){
				$scope.currentUser = "Guest";
			}

			var postObject = {user: $scope.currentUser, message: $scope.newPost, time: new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')};

			$scope.currentThread.posts.push(postObject);

			var stringifiedPost = JSON.stringify(postObject);
			$http({method: "POST", url: "http://localhost:3000/posts", data: stringifiedPost})

			$scope.newPost = "";
		}

		$scope.addThread = function(){
			if($scope.currentUser == "" || $scope.currentUser == null){
				$scope.currentUser = "Guest";
			}
			$scope.forum.threads.push({
				subject: $scope.newSubject,
				posts: [{user: $scope.currentUser, message: $scope.newPost, time: new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')}]
			});

			$scope.newSubject = "";
			$scope.newPost = "";
		}

		$scope.currentUser = "";
		$scope.newPost = "";
		$scope.newSubject = "";
		$scope.predicate = '';
	});
<|endoftext|>


<commit_before>config = require('./../config/config.js');
var request = require("request");
var tidy = require('htmltidy').tidy;
var pg = require('pg');
var htmltidyOptions = require('htmltidy-options');

module.exports = function(app, passport) {

    // INDEX PAGE ===============================
    app.get('/dbmiannotator', function(req, res) {
	    if (req.isAuthenticated()){
            res.redirect('/dbmiannotator/main');
	    } else {
	        res.render('index.ejs', { message: req.flash('loginMessage') });
	    }
    });

    // LOGIN ===============================
    app.get('/dbmiannotator/login', function(req, res) {
        // render the page and pass in any flash data if it exists
        res.render('login.ejs', { message: req.flash('loginMessage') });
    });

    app.post('/dbmiannotator/login', passport.authenticate('local-login', {
        successRedirect : '/dbmiannotator/main',
        failureRedirect : '/dbmiannotator/login',
        failureFlash : true
    }));

    // SIGNUP ==============================
    app.get('/dbmiannotator/register', function(req, res) {
        res.render('register.ejs', { message: req.flash('signupMessage') });
    });

    app.post('/dbmiannotator/register', isRegisterFormValid, passport.authenticate('local-signup', {
	successRedirect : '/dbmiannotator/main',
	failureRedirect : '/dbmiannotator/register',
	failureFlash : true
    })
	    );

    // MAIN ==============================
    app.get('/dbmiannotator/main', isLoggedIn, initPluginProfile, function(req, res) {
        if (config.profile.userProfile != null) {
            annotationType = config.profile.userProfile.type;
        } else {
            annotationType = config.profile.def;
        }

	    // fetch all DDI annotations for current user
	    var url = "http://" + config.store.host +":" + config.store.port + "/search?email=" + req.user.email + "&annotationType=" + annotationType;

	    request({url: url, json: true}, function(error,response,body){
	        if (!error && response.statusCode === 200) {

		        res.render('main.ejs', {
		            user : req.user,
		            annotations : body,
		            exportMessage : req.flash('exportMessage'),
		            loadMessage : req.flash('loadMessage'),
		            host : config.annotator.host,
		        });

	        } else {
		        res.render('main.ejs', {
		            user : req.user,
		            annotations : {'total':0},
		            exportMessage: req.flash('exportMessage'),
		            loadMessage: req.flash('loadMessage'),
		            host: config.annotator.host,
		        });
	        }
	    });
    });

    // LOGOUT ==============================
    app.get('/dbmiannotator/logout', function(req, res) {
        config.profile.pluginSetL = [];
        config.profile.userProfile = {};
        req.logout();
        res.redirect('/dbmiannotator');
    });

    // DISPLAY ==============================
    app.get('/dbmiannotator/displayWebPage', isLoggedIn, praseWebContents, function(req, res) {

	    var sourceUrl = req.query.sourceURL.trim();
	    var email = req.query.email;
	    var validUrl = require('valid-url');

	    if (validUrl.isUri(sourceUrl)){

		// if (sourceUrl.match(/\.pdf/g)){ // local pdf resouces
		//     res.redirect("/dbmiannotator/viewer.html?file=" + sourceUrl+"&email=" + email);
		// }

		if (sourceUrl.match(/localhost.*html/g)) {
                    res.render('displayWebPage.ejs', {
			htmlsource: req.htmlsource,
			pluginSetL: config.profile.pluginSetL,
			userProfile: config.profile.userProfile
                    });
	        }
		else {
		    req.flash('loadMessage', 'The url you just entered is not valid!');
		    res.redirect('/dbmiannotator/main');
		}

	    }
    });

    // PLUGIN PROFILE ==============================
    app.post('/dbmiannotator/savePluginProfile', function(req, res) {
        console.log(req.body.pluginset);
        console.log(req.user.email);

        pg.connect(config.postgres, function(err, client, done) {

            if(err) {
                done();
                console.log(err);
                return res.status(500).json({ success: false, data: err});
            }

            if (config.profile.userProfile == null){  // no user profile, then insert
                console.log("[ERROR] user profile not avaliable!");
	            res.redirect('/dbmiannotator/main');

            } else {   // get user profile
                resultsL = []
                var queryUserProfile = client.query('SELECT u.uid, up.set_id FROM "user" u, user_profile up where u.uid = up.uid and u.email = $1 and up.status = $2', [req.user.email, true]);
                queryUserProfile.on('row', function(row) {
                    resultsL.push(row);
                });

                queryUserProfile.on('end', function() { // user profile changed, then update
                    if (resultsL[0].set_id != req.body.pluginset){
                        console.log("update user plugin profile");
                        client.query('UPDATE "user_profile" SET set_id = $1, created = now() FROM (SELECT * FROM "user" u where u.email = $2) AS u, "user_profile" up WHERE up.uid = u.uid AND up.status = true', [req.body.pluginset, req.user.email]);
                        done();
	                res.redirect('/dbmiannotator/main');
                    }
                    else {
                        console.log("user profile not need to update");
                        done();
	                res.redirect('/dbmiannotator/main');
                    }
                }, function(){
                    res.redirect('/dbmiannotator/main');
                });
            }

        });
    });

    // EXPORT TO JSON ==============================
    app.get('/dbmiannotator/exportjson', isLoggedIn, function(req, res){

	    var url = "http://" + config.store.host + ":" + config.store.port + "/search?email=" + req.query.email + "&annotationType=" + config.profile.def;

	    request({url: url, json: true}, function(error,response,body){
	        if (!error && response.statusCode === 200) {

                var annsJsonRes = JSON.stringify(body.rows);
                res.attachment('annotations-'+req.query.email+'.json');
		        res.setHeader('Content-Type', 'application/json');
		        res.end(annsJsonRes);
	        } else {
		        res.redirect('/dbmiannotator/main');
	        }
	    });
    });

    // EXPORT TO CSV ==============================
    app.get('/dbmiannotator/exportcsv', isLoggedIn, function(req, res){

	    var url = "http://" + config.store.host + ":" + config.store.port + "/search?email=" + req.query.email + "&annotationType=" + config.profile.def;

	    request({url: url, json: true}, function(error,response,body){
	        if (!error && response.statusCode === 200) {

                var jsonObjs = body.rows;
                res.attachment('annotations-'+req.query.email+'.csv');
		        res.setHeader('Content-Type', 'text/csv');
                var csvTxt = '"document"\t"claim label"\t"claim text"\t"method"\t"relationship"\t"drug1"\t"drug2"\t"precipitant"\t"enzyme"\t"participants"\t"participants text"\t"drug1 dose"\t"drug1 formulation"\t"drug1 duration"\t"drug1 regimens"\t"drug1 dose text"\t"drug2 dose"\t"drug2 formulation"\t"drug2 duration"\t"drug2 regimens"\t"drug2 dose text"\t"auc"\t"auc type"\t"auc direction"\t"auc text"\t"cmax"\t"cmax type"\t"cmax direction"\t"cmax text"\t"cl"\t"cl type"\t"cl direction"\t"cl text"\t"halflife"\t"halflife type"\t"halflife direction"\t"halflife text"\t"group randomization"\t"parallel group design"\n';

                for (var i = 0; i < jsonObjs.length; i++) {
                    jsonObj = jsonObjs[i];
                    claim = jsonObj.argues;
                    dataL = claim.supportsBy;

                    var line = '"' + jsonObj.rawurl + '"\t"' + claim.label + '"\t"' + claim.hasTarget.hasSelector.exact + '"\t"' + claim.method + '"\t"' + claim.qualifiedBy.relationship + '"\t"' + claim.qualifiedBy.drug1 + '"\t"' + claim.qualifiedBy.drug2 + '"\t"' + (claim.qualifiedBy.precipitant || '') + '"\t"' + (claim.qualifiedBy.enzyme || '' ) + '"';
                    //+ '"\t"' + (claim.grouprandom || '') + '"\t"' + (claim.parallelgroup || '')
                    for (var j = 0; j < dataL.length; j ++) {
                        data = dataL[j];
                        method = data.supportsBy;
                        material = method.supportsBy;

                        if (material.participants != null)
                            line += '\t"' + (material.participants.value || '') + '"\t"' + (getSpanFromField(material.participants) || '') + '"';
                        else
                            line += '\t\t';

                        if (material.drug1Dose != null)
                            line += '\t"' + (material.drug1Dose.value || '') + '"\t"' + (material.drug1Dose.formulation || '') + '"\t"'  + (material.drug1Dose.duration || '') + '"\t"' + (material.drug1Dose.regimens || '') + '"\t"' + (getSpanFromField(material.drug1Dose) || '') + '"';
                        else
                            line += '\t\t\t\t';

                        if (material.drug2Dose != null)
                            line += '\t"' + (material.drug2Dose.value || '') + '"\t"' + (material.drug2Dose.formulation || '') + '"\t"'  + (material.drug2Dose.duration || '') + '"\t"' + (material.drug2Dose.regimens || '') + '"\t"' + (getSpanFromField(material.drug2Dose) || '') + '"';
                        else
                            line += '\t\t\t\t';

                        dataFieldsL = ["auc","cmax","clearance","halflife"];
                        for (p = 0; p < dataFieldsL.length; p++) {
                            field = dataFieldsL[p];
                            if (data[field] != null)
                                line += '\t"' + (data[field].value || '') + '"\t"' + (data[field].direction || '') + '"\t"' + (data[field].type || '') + '"\t"' + (getSpanFromField(data[field]) || '') + '"';
                            else
                                line += '\t\t\t\t';
                        }

                        if (data.grouprandom != null)
                            line += '\t"' + (data.grouprandom || '') + '"';
                        else
                            line += '\t';

                        if (data.parallelgroup != null)
                            line += '\t"' + (data.parallelgroup || '') + '"';
                        else
                            line += '\t';

                    }
                    csvTxt += line + "\n";
                }

		        res.attachment('annotations-'+req.query.email+'.csv');
		        res.setHeader('Content-Type', 'text/csv');
		        res.end(csvTxt);

	        } else {
		        //req.flash('exportMessage', 'exported failed, annotation fetch exception, please see logs or contact Yifan at yin2@pitt.edu!');
		        res.redirect('/dbmiannotator/main');
	        }
	    });
    });
};

// UTIL FUNCTIONS ==============================
function getSpanFromField(field) {
    if (field.hasTarget !=null) {
        if (field.hasTarget.hasSelector !=null)
            return field.hasTarget.hasSelector.exact;
    } else {
        return "";
    }
}

// MIDDLE WARE FUNCTIONS ==============================

// parse web contents from url
function praseWebContents(req, res, next){
    var sourceUrl = req.query.sourceURL.trim();

    if(sourceUrl.match(/localhost.*html/g)){
        var options = {
            host: sourceUrl,
            method: 'POST'
        }
        //var cheerio = require("cheerio");

        request(sourceUrl, function(err, res, body){

            labelDecode = body.replace(/&/g,'&').replace(/ /g,' ');

            // normalize html source
            tidy(labelDecode, htmltidyOptions['Kastor tidy - XHTML Clean page UTF-8'], function(err, html) {
                if (err){
                    console.log(err);
                }
                req.htmlsource = html;
                next();
            });

        });

    } else {
        next();
    }
}

// get plugin profile
function initPluginProfile(req, res, next){
    var pluginSetL = [];
    var userProfileL = [];

    var data = {text: req.body.text, complete: false};

    pg.connect(config.postgres, function(err, client, done) {

        if(err) {
            done();
            console.log(err);
            return res.status(500).json({ success: false, data: err});
        }
        // get all available plugin sets
        var queryPlugins = client.query("SELECT ps.id, ps.name, ps.type, ps.description FROM plugin_set ps WHERE ps.status = True ORDER BY ps.id ASC;");

        queryPlugins.on('row', function(row) {
            pluginSetL.push(row);
        });

        queryPlugins.on('end', function() {
            config.profile.pluginSetL = pluginSetL;
        });
        // get plugin set from user setting
        var queryUserProfile = client.query('SELECT u.uid, up.set_id, ps.name, ps.type FROM "user" u, "user_profile" up, "plugin_set" ps where u.uid = up.uid and up.set_id = ps.id and u.email = $1 and up.status = True', [req.user.email]);

        queryUserProfile.on('row', function(row) {
            userProfileL.push(row);
        });
        queryUserProfile.on('end', function() {
            if (userProfileL.length > 0){
                config.profile.userProfile = userProfileL[0];
                console.log("init pluginProfile - get user profile");
                console.log(config.profile.userProfile)
            }
            next();
            client.end();
        });

    });
}

// route middleware to make sure a user is logged in
function isLoggedIn(req, res, next) {

    // if user is authenticated in the session, carry on
    if (req.isAuthenticated())
        return next();

    // if they aren't redirect them to the home page
    res.redirect('/dbmiannotator');
}

// validate inputs in register form
function isRegisterFormValid(req, res, next){

    req.assert('email', 'Email is not valid').isEmail();
    req.assert('username', 'Username must be at least 4 characters long').len(4);
    req.assert('password', 'Password must be at least 6 characters long').len(6);
    req.assert('repassword', 'Passwords do not match').equals(req.body.password);

    var errors = req.validationErrors();

    if (errors) {

	res.render('register', {
	    title: 'Register Form Validation',
	    message: '',
	    errors: errors
        });

    } else {
	return next();
    }

}

<commit_msg>fix export annotation as csv<commit_after>config = require('./../config/config.js');
var request = require("request");
var tidy = require('htmltidy').tidy;
var pg = require('pg');
var htmltidyOptions = require('htmltidy-options');

module.exports = function(app, passport) {

    // INDEX PAGE ===============================
    app.get('/dbmiannotator', function(req, res) {
	    if (req.isAuthenticated()){
            res.redirect('/dbmiannotator/main');
	    } else {
	        res.render('index.ejs', { message: req.flash('loginMessage') });
	    }
    });

    // LOGIN ===============================
    app.get('/dbmiannotator/login', function(req, res) {
        // render the page and pass in any flash data if it exists
        res.render('login.ejs', { message: req.flash('loginMessage') });
    });

    app.post('/dbmiannotator/login', passport.authenticate('local-login', {
        successRedirect : '/dbmiannotator/main',
        failureRedirect : '/dbmiannotator/login',
        failureFlash : true
    }));

    // SIGNUP ==============================
    app.get('/dbmiannotator/register', function(req, res) {
        res.render('register.ejs', { message: req.flash('signupMessage') });
    });

    app.post('/dbmiannotator/register', isRegisterFormValid, passport.authenticate('local-signup', {
	successRedirect : '/dbmiannotator/main',
	failureRedirect : '/dbmiannotator/register',
	failureFlash : true
    })
	    );

    // MAIN ==============================
    app.get('/dbmiannotator/main', isLoggedIn, initPluginProfile, function(req, res) {
        if (config.profile.userProfile != null) {
            annotationType = config.profile.userProfile.type;
        } else {
            annotationType = config.profile.def;
        }

	    // fetch all DDI annotations for current user
	    var url = "http://" + config.store.host +":" + config.store.port + "/search?email=" + req.user.email + "&annotationType=" + annotationType;

	    request({url: url, json: true}, function(error,response,body){
	        if (!error && response.statusCode === 200) {

		        res.render('main.ejs', {
		            user : req.user,
		            annotations : body,
		            exportMessage : req.flash('exportMessage'),
		            loadMessage : req.flash('loadMessage'),
		            host : config.annotator.host,
		        });

	        } else {
		        res.render('main.ejs', {
		            user : req.user,
		            annotations : {'total':0},
		            exportMessage: req.flash('exportMessage'),
		            loadMessage: req.flash('loadMessage'),
		            host: config.annotator.host,
		        });
	        }
	    });
    });

    // LOGOUT ==============================
    app.get('/dbmiannotator/logout', function(req, res) {
        config.profile.pluginSetL = [];
        config.profile.userProfile = {};
        req.logout();
        res.redirect('/dbmiannotator');
    });

    // DISPLAY ==============================
    app.get('/dbmiannotator/displayWebPage', isLoggedIn, praseWebContents, function(req, res) {

	    var sourceUrl = req.query.sourceURL.trim();
	    var email = req.query.email;
	    var validUrl = require('valid-url');

	    if (validUrl.isUri(sourceUrl)){

		    // if (sourceUrl.match(/\.pdf/g)){ // local pdf resouces
		    //     res.redirect("/dbmiannotator/viewer.html?file=" + sourceUrl+"&email=" + email);
		    // }

		    if (sourceUrl.match(/localhost.*html/g)) {
                res.render('displayWebPage.ejs', {
			        htmlsource: req.htmlsource,
			        pluginSetL: config.profile.pluginSetL,
			        userProfile: config.profile.userProfile
                });
	        }
		    else {
		        req.flash('loadMessage', 'The url you just entered is not valid!');
		        res.redirect('/dbmiannotator/main');
		    }

	    }
    });

    // PLUGIN PROFILE ==============================
    app.post('/dbmiannotator/savePluginProfile', function(req, res) {
        console.log(req.body.pluginset);
        console.log(req.user.email);

        pg.connect(config.postgres, function(err, client, done) {

            if(err) {
                done();
                console.log(err);
                return res.status(500).json({ success: false, data: err});
            }

            if (config.profile.userProfile == null){  // no user profile, then insert
                console.log("[ERROR] user profile not avaliable!");
	            res.redirect('/dbmiannotator/main');

            } else {   // get user profile
                resultsL = []
                var queryUserProfile = client.query('SELECT u.uid, up.set_id FROM "user" u, user_profile up where u.uid = up.uid and u.email = $1 and up.status = $2', [req.user.email, true]);
                queryUserProfile.on('row', function(row) {
                    resultsL.push(row);
                });

                queryUserProfile.on('end', function() { // user profile changed, then update
                    if (resultsL[0].set_id != req.body.pluginset){
                        console.log("update user plugin profile");
                        client.query('UPDATE "user_profile" SET set_id = $1, created = now() FROM (SELECT * FROM "user" u where u.email = $2) AS u, "user_profile" up WHERE up.uid = u.uid AND up.status = true', [req.body.pluginset, req.user.email]);
                        done();
	                res.redirect('/dbmiannotator/main');
                    }
                    else {
                        console.log("user profile not need to update");
                        done();
	                res.redirect('/dbmiannotator/main');
                    }
                }, function(){
                    res.redirect('/dbmiannotator/main');
                });
            }

        });
    });

    // EXPORT TO JSON ==============================
    app.get('/dbmiannotator/exportjson', isLoggedIn, function(req, res){

	    var url = "http://" + config.store.host + ":" + config.store.port + "/search?email=" + req.query.email + "&annotationType=" + config.profile.def;

	    request({url: url, json: true}, function(error,response,body){
	        if (!error && response.statusCode === 200) {

                var annsJsonRes = JSON.stringify(body.rows);
                res.attachment('annotations-'+req.query.email+'.json');
		        res.setHeader('Content-Type', 'application/json');
		        res.end(annsJsonRes);
	        } else {
		        res.redirect('/dbmiannotator/main');
	        }
	    });
    });

    // EXPORT TO CSV ==============================
    app.get('/dbmiannotator/exportcsv', isLoggedIn, function(req, res){

	    var url = "http://" + config.store.host + ":" + config.store.port + "/search?email=" + req.query.email + "&annotationType=" + config.profile.def;

	    request({url: url, json: true}, function(error,response,body){
	        if (!error && response.statusCode === 200) {

                var jsonObjs = body.rows;
                res.attachment('annotations-'+req.query.email+'.csv');
		        res.setHeader('Content-Type', 'text/csv');
                var csvTxt = '"document"\t"claim label"\t"claim text"\t"method"\t"relationship"\t"drug1"\t"drug2"\t"precipitant"\t"enzyme"\t"participants"\t"participants text"\t"drug1 dose"\t"drug1 formulation"\t"drug1 duration"\t"drug1 regimens"\t"drug1 dose text"\t"drug2 dose"\t"drug2 formulation"\t"drug2 duration"\t"drug2 regimens"\t"drug2 dose text"\t"auc"\t"auc type"\t"auc direction"\t"auc text"\t"cmax"\t"cmax type"\t"cmax direction"\t"cmax text"\t"cl"\t"cl type"\t"cl direction"\t"cl text"\t"halflife"\t"halflife type"\t"halflife direction"\t"halflife text"\t"group randomization"\t"parallel group design"\n';

                for (var i = 0; i < jsonObjs.length; i++) {
                    jsonObj = jsonObjs[i];
                    claim = jsonObj.argues;
                    dataL = claim.supportsBy;

                    var claimRow = '"' + jsonObj.rawurl + '"\t"' + claim.label + '"\t"' + claim.hasTarget.hasSelector.exact + '"\t"' + claim.method + '"\t"' + claim.qualifiedBy.relationship + '"\t"' + claim.qualifiedBy.drug1 + '"\t"' + claim.qualifiedBy.drug2 + '"\t"' + (claim.qualifiedBy.precipitant || '') + '"\t"' + (claim.qualifiedBy.enzyme || '' ) + '"';

                    for (var j = 0; j < dataL.length; j++) {
                        var data = dataL[j];
                        var method = data.supportsBy;
                        var material = method.supportsBy;
                        var dataRow = ""

                        if (material.participants != null)
                            dataRow += '\t"' + (material.participants.value || '') + '"\t"' + (getSpanFromField(material.participants) || '') + '"';
                        else
                            dataRow += '\t\t';

                        if (material.drug1Dose != null)
                            dataRow += '\t"' + (material.drug1Dose.value || '') + '"\t"' + (material.drug1Dose.formulation || '') + '"\t"'  + (material.drug1Dose.duration || '') + '"\t"' + (material.drug1Dose.regimens || '') + '"\t"' + (getSpanFromField(material.drug1Dose) || '') + '"';
                        else
                            dataRow += '\t\t\t\t';

                        if (material.drug2Dose != null)
                            dataRow += '\t"' + (material.drug2Dose.value || '') + '"\t"' + (material.drug2Dose.formulation || '') + '"\t"'  + (material.drug2Dose.duration || '') + '"\t"' + (material.drug2Dose.regimens || '') + '"\t"' + (getSpanFromField(material.drug2Dose) || '') + '"';
                        else
                            dataRow += '\t\t\t\t';

                        dataFieldsL = ["auc","cmax","clearance","halflife"];
                        for (p = 0; p < dataFieldsL.length; p++) {
                            field = dataFieldsL[p];
                            if (data[field] != null)
                                dataRow += '\t"' + (data[field].value || '') + '"\t"' + (data[field].direction || '') + '"\t"' + (data[field].type || '') + '"\t"' + (getSpanFromField(data[field]) || '') + '"';
                            else
                                dataRow += '\t\t\t\t';
                        }

                        if (data.grouprandom != null)
                            dataRow += '\t"' + (data.grouprandom || '') + '"';
                        else
                            dataRow += '\t';

                        if (data.parallelgroup != null)
                            dataRow += '\t"' + (data.parallelgroup || '') + '"';
                        else
                            dataRow += '\t';

                        csvTxt += claimRow + dataRow + "\n";
                    }
                }

		        res.attachment('annotations-'+req.query.email+'.csv');
		        res.setHeader('Content-Type', 'text/csv');
		        res.end(csvTxt);

	        } else {
		        //req.flash('exportMessage', 'exported failed, annotation fetch exception, please see logs or contact Yifan at yin2@pitt.edu!');
		        res.redirect('/dbmiannotator/main');
	        }
	    });
    });
};

// UTIL FUNCTIONS ==============================
function getSpanFromField(field) {
    if (field.hasTarget !=null) {
        if (field.hasTarget.hasSelector !=null)
            return field.hasTarget.hasSelector.exact;
    } else {
        return "";
    }
}

// MIDDLE WARE FUNCTIONS ==============================

// parse web contents from url
function praseWebContents(req, res, next){
    var sourceUrl = req.query.sourceURL.trim();

    if(sourceUrl.match(/localhost.*html/g)){
        var options = {
            host: sourceUrl,
            method: 'POST'
        }
        //var cheerio = require("cheerio");

        request(sourceUrl, function(err, res, body){

            labelDecode = body.replace(/&/g,'&').replace(/ /g,' ');

            // normalize html source
            tidy(labelDecode, htmltidyOptions['Kastor tidy - XHTML Clean page UTF-8'], function(err, html) {
                if (err){
                    console.log(err);
                }
                req.htmlsource = html;
                next();
            });

        });

    } else {
        next();
    }
}

// get plugin profile
function initPluginProfile(req, res, next){
    var pluginSetL = [];
    var userProfileL = [];

    var data = {text: req.body.text, complete: false};

    pg.connect(config.postgres, function(err, client, done) {

        if(err) {
            done();
            console.log(err);
            return res.status(500).json({ success: false, data: err});
        }
        // get all available plugin sets
        var queryPlugins = client.query("SELECT ps.id, ps.name, ps.type, ps.description FROM plugin_set ps WHERE ps.status = True ORDER BY ps.id ASC;");

        queryPlugins.on('row', function(row) {
            pluginSetL.push(row);
        });

        queryPlugins.on('end', function() {
            config.profile.pluginSetL = pluginSetL;
        });
        // get plugin set from user setting
        var queryUserProfile = client.query('SELECT u.uid, up.set_id, ps.name, ps.type FROM "user" u, "user_profile" up, "plugin_set" ps where u.uid = up.uid and up.set_id = ps.id and u.email = $1 and up.status = True', [req.user.email]);

        queryUserProfile.on('row', function(row) {
            userProfileL.push(row);
        });
        queryUserProfile.on('end', function() {
            if (userProfileL.length > 0){
                config.profile.userProfile = userProfileL[0];
                console.log("init pluginProfile - get user profile");
                console.log(config.profile.userProfile)
            }
            next();
            client.end();
        });

    });
}

// route middleware to make sure a user is logged in
function isLoggedIn(req, res, next) {

    // if user is authenticated in the session, carry on
    if (req.isAuthenticated())
        return next();

    // if they aren't redirect them to the home page
    res.redirect('/dbmiannotator');
}

// validate inputs in register form
function isRegisterFormValid(req, res, next){

    req.assert('email', 'Email is not valid').isEmail();
    req.assert('username', 'Username must be at least 4 characters long').len(4);
    req.assert('password', 'Password must be at least 6 characters long').len(6);
    req.assert('repassword', 'Passwords do not match').equals(req.body.password);

    var errors = req.validationErrors();

    if (errors) {

	res.render('register', {
	    title: 'Register Form Validation',
	    message: '',
	    errors: errors
        });

    } else {
	return next();
    }

}

<|endoftext|>


<commit_before>/*
Requires parameters so load https://megjlow.github.io/extensionloader.js and use that block to load this one.
Parameters are:
name - 	If you have a router set up which will assign the same IP address to a tile every time and will do DNS then the tile can be addressed
		by name e.g. http://SUN/XXX will work if SUN resolves to the IP address of a tile e.g. https://megjlow.github.io/extension2.js?name=SUN
ip -	If you don't have a router set up to assign the same IP address to a tile every time or you don't have DNS which will resolve the tile
		name then use the IP parameter e.g. https://megjlow.github.io/extension2.js?ip=10.0.0.1 will load the tile extension which communicates
		with a tile at the IP address 10.0.0.1

If both name and ip are supplied then name will be used before ip.
Digital pins 1, 2 and 3 map to the Feather Huzzah pins 12, 13 and 14
Pwm pins 1 and 2 map to the Feather Huzza pins 4 and 5
*/

new (function() {
	var ext = this;
	ext.isReady = false;
	ext.socket = null;
	ext.messageQueue = {};

 	var getUrlParameter = function getUrlParameter(sParam) {
	    var sPageURL = decodeURIComponent(document.currentScript.src.split("?")[1]),
	        sURLVariables = sPageURL.split('&'),
	        sParameterName,
	        i;

	    for (i = 0; i < sURLVariables.length; i++) {
	        sParameterName = sURLVariables[i].split('=');

	        if (sParameterName[0] === sParam) {
	            return sParameterName[1] === undefined ? true : sParameterName[1];
	        }
	    }
	};

	ext.name = getUrlParameter('name');
	ext.ip = getUrlParameter('ip');

	if(ext.name != undefined) {
		// we'll use the name supplied by preference
		ext.url = "http://" + ext.name;
		console.log('got name');
	}
	else {
		// no name supplied so use the IP address
		ext.url = "http://" + ext.ip;
		console.log("didn't get name");
	}

	console.log('name ' + ext.name);
	console.log('ip ' + ext.ip);
	console.log('using url ' + ext.url);

	var descriptor = {
    	blocks: [
      		[' ', ext.name + ': digital pin %m.pin setting %m.dsetting', 'setDigital', '1', 'off'],
      		[' ', ext.name + ': pwm pin %m.ppin setting %n', 'setPwm', '1', '100'],
      		[' ', ext.name + ': digital pin %m.pin get', 'getDigital', '1'],
      		[' ', ext.name + ': pwm pin %m.ppin get', 'getPwm', '1']
    	],
    	'menus': {
      		'pin': ['1', '2', '3'],
      		'dsetting': ['on', 'off'],
      		'ppin': ['1', '2']
     	},
    	url: 'http://www.warwick.ac.uk/tilesfortales'
  	};

  	if(ext.name == undefined) {
  		var descriptor = {
	    	blocks: [
				[' ', ext.ip + ': connect', 'connect'],
				[' ', ext.ip + ': disconnect', 'disconnect'],
				['b', ext.ip + ': isConnected', 'isConnected'],
				[' ', ext.ip + ': setPinMode %m.pin %m.io %m.ioMode', 'setPinMode', 0, 'output', 'digital'],
	      		[' ', ext.ip + ': digital pin %m.pin setting %m.dsetting', 'setDigital', '1', 'off'],
	      		[' ', ext.ip + ': pwm pin %m.ppin setting %n', 'setPwm', '1', '100'],
	      		['R', ext.ip + ': digital pin %m.pin get', 'getDigital', '1'],
	      		[' ', ext.ip + ': pwm pin %m.ppin get', 'getPwm', '1'],
	      		[' ', ext.ip + ': report digital callback %m.pin %m.enableDisable', 'reportDigital', '1', 'enable']
	    	],
	    	'menus': {
	      		'pin': ['12', '2', '3'],
	      		'dsetting': ['on', 'off'],
	      		'ppin': ['1', '2'],
				'io': ['output', 'input', 'pwm', 'analog'],
				'ioMode': ['digital', 'pwm'],
				'enableDisable': ['enable', 'disable']
	     	},
	    	url: 'http://www.warwick.ac.uk/tilesfortales'
	  	};
  	}

	ext._shutdown = function() {};

	ext.reportDigital = function(pin, setting) {
		console.log('reportDigital ' + pin + ' ' + setting);
		var bytearray = new Uint8Array(2);
		bytearray[0] = 0xD0 | pin; // report digital
		if('enable' == setting) {
			bytearray[1] = 1; // disable/enable
		}
		else {
			bytearray[1] = 0;
		}
		ext.socket.send(bytearray.buffer);
	}

  	ext.setPinMode = function(pin, mode) {
		if(ext.isConnected()) {
			var setting = 0x00;
		    console.log("setPinMode pin:" + pin + " mode:" + mode);
		    if('output' == mode) {
		    	setting = 0x01;
		    }
		    else if('analog' == mode) {
		    	setting = 0x02;
		    }
		    else if('pwm' == mode) {
		    	setting = 0x03;
		    }
		    var bytearray = new Uint8Array(3);
		    bytearray[0] = 0xF4;// PIN_MODE;
		    bytearray[1] = pin;
		    bytearray[2] = setting;
		    ext.socket.send(bytearray.buffer);
		}
  	}

	ext.isConnected = function() {
		var retval = false;
		if(ext.socket != null && ext.socket.readyState == ext.socket.OPEN) {
			retval = true;
		}
		return retval;
	}

	ext.disconnect = function() {
		if(ext.socket != null) {
			ext.socket.close();
			ext.socket = null;
		}
	}

	ext.onMessage = function(evt) {
		var dv = new DataView(evt.data);
		var r = "received message: ";
		for(var i=0; i<dv.byteLength; i++) {
			r = r + " " + dv.getUint8(i).toString(16);
		}

		if(dv.byteLength > 0) {
			if(dv.getUint8(0) == 0xF0) { // start sysex
				if(dv.getUint8(1) == 0x6E) { // pin state response
					// 2 will be pin number, 4 will be state
					var pin = dv.getUint8(2);
					var state = dv.getUint8(4);
					console.log("state " + state);
					console.log("state " + state >> 7);
					if(ext.messageQueue["pin-state-" + pin] != undefined) {
						console.log("got handler");
						ext.messageQueue["pin-state-" + pin](state);
					}
				}
			}
		}

		// 6e PIN_STATE_RESPONSE

		console.log(r);
	}

	ext.doSend = function(message) {
		console.log("SENT: " + message);
		ext.socket.send(message);
	}

	ext.onOpen = function(evt) {
		console.log("Connected");
		//ext.doSend("WebSocket rocks");
	}

	ext.onClose = function(evt) {
		console.log("onClose");
		ext.socket = null;
	}

	ext._getStatus = function() {
		var retval = {status: 1, msg: 'Not Connected'};
		if(ext.socket != null && ext.socket.readyState == ext.socket.OPEN) {
			retval = {status: 2, msg: 'Device connected'};
		}
		return retval;
	}

	ext.connect = function() {
		if(ext.socket == null) {
			ext.socket = new WebSocket("ws://" + ext.ip, 'firmata');
			ext.socket.binaryType = "arraybuffer";
			ext.socket.onopen = function(evt) {ext.onOpen(evt)};
			ext.socket.onmessage = function(evt) {ext.onMessage(evt)};
			ext.socket.onclose = function(evt) {ext.onClose(evt)};
		}
		else if(ext.socket.readyState == ext.socket.CLOSING || ext.socket.readyState == ext.socket.CLOSED) {
			ext.socket = null;
			ext.connect();
		}
		// if socket is in open or connecting state we're not going to do anything
	}

	ext.getPwm = function(pin) {
	};

	ext.setPwm = function(pin, setting) {
		var p = 4;
		if(pin == 2) {
			p = 5;
		}
		var url = ext.url + '/gpio' + p + '/' + setting;
		$.ajax({
			type: 'POST',
			url: url,
			success: function(response) {
			}
		});
		setTimeout(function(){ }, 10);
	}

  	ext.getDigital = function(pin, callback) {
  		if(ext.isConnected()) {
  			console.log("getDigital pin:" + pin);
	  		var bytearray = new Uint8Array(4);
	  		bytearray[0] = 0xF0; // Start sysex
	  		bytearray[1] = 0x6D; // PIN_STATE_QUERY
	  		bytearray[2] = pin;
	  		bytearray[3] = 0xF7; // end sysex
	  		ext.socket.send(bytearray);

			ext.messageQueue["pin-state-" + pin] = callback;
		}
	};

	ext.setDigital = function(pin, setting) {
		if(ext.isConnected()) {
			console.log("setDigital pin:" + pin + " setting:" + setting);
		    var s = 1;
		    if(setting == 'off') {
		      s = 0;
		    }
		    var bytearray = new Uint8Array(3);
		    bytearray[0] = 0xF5; // set digital pin value
		    bytearray[1] = pin;
		    bytearray[2] = s;

		    ext.socket.send(bytearray);
	    }
	}

	if(ext.name != undefined) {
		ScratchExtensions.register(ext.name, descriptor, ext);
	}
	else {
		ScratchExtensions.register(ext.ip, descriptor, ext);
	}

})();
<commit_msg>reportDigital<commit_after>/*
Requires parameters so load https://megjlow.github.io/extensionloader.js and use that block to load this one.
Parameters are:
name - 	If you have a router set up which will assign the same IP address to a tile every time and will do DNS then the tile can be addressed
		by name e.g. http://SUN/XXX will work if SUN resolves to the IP address of a tile e.g. https://megjlow.github.io/extension2.js?name=SUN
ip -	If you don't have a router set up to assign the same IP address to a tile every time or you don't have DNS which will resolve the tile
		name then use the IP parameter e.g. https://megjlow.github.io/extension2.js?ip=10.0.0.1 will load the tile extension which communicates
		with a tile at the IP address 10.0.0.1

If both name and ip are supplied then name will be used before ip.
Digital pins 1, 2 and 3 map to the Feather Huzzah pins 12, 13 and 14
Pwm pins 1 and 2 map to the Feather Huzza pins 4 and 5
*/

new (function() {
	var ext = this;
	ext.isReady = false;
	ext.socket = null;
	ext.messageQueue = {};

 	var getUrlParameter = function getUrlParameter(sParam) {
	    var sPageURL = decodeURIComponent(document.currentScript.src.split("?")[1]),
	        sURLVariables = sPageURL.split('&'),
	        sParameterName,
	        i;

	    for (i = 0; i < sURLVariables.length; i++) {
	        sParameterName = sURLVariables[i].split('=');

	        if (sParameterName[0] === sParam) {
	            return sParameterName[1] === undefined ? true : sParameterName[1];
	        }
	    }
	};

	ext.name = getUrlParameter('name');
	ext.ip = getUrlParameter('ip');

	if(ext.name != undefined) {
		// we'll use the name supplied by preference
		ext.url = "http://" + ext.name;
		console.log('got name');
	}
	else {
		// no name supplied so use the IP address
		ext.url = "http://" + ext.ip;
		console.log("didn't get name");
	}

	console.log('name ' + ext.name);
	console.log('ip ' + ext.ip);
	console.log('using url ' + ext.url);

	var descriptor = {
    	blocks: [
      		[' ', ext.name + ': digital pin %m.pin setting %m.dsetting', 'setDigital', '1', 'off'],
      		[' ', ext.name + ': pwm pin %m.ppin setting %n', 'setPwm', '1', '100'],
      		[' ', ext.name + ': digital pin %m.pin get', 'getDigital', '1'],
      		[' ', ext.name + ': pwm pin %m.ppin get', 'getPwm', '1']
    	],
    	'menus': {
      		'pin': ['1', '2', '3'],
      		'dsetting': ['on', 'off'],
      		'ppin': ['1', '2']
     	},
    	url: 'http://www.warwick.ac.uk/tilesfortales'
  	};

  	if(ext.name == undefined) {
  		var descriptor = {
	    	blocks: [
				[' ', ext.ip + ': connect', 'connect'],
				[' ', ext.ip + ': disconnect', 'disconnect'],
				['b', ext.ip + ': isConnected', 'isConnected'],
				[' ', ext.ip + ': setPinMode %m.pin %m.io %m.ioMode', 'setPinMode', 0, 'output', 'digital'],
	      		[' ', ext.ip + ': digital pin %m.pin setting %m.dsetting', 'setDigital', '1', 'off'],
	      		[' ', ext.ip + ': pwm pin %m.ppin setting %n', 'setPwm', '1', '100'],
	      		['R', ext.ip + ': digital pin %m.pin get', 'getDigital', '1'],
	      		[' ', ext.ip + ': pwm pin %m.ppin get', 'getPwm', '1'],
	      		[' ', ext.ip + ': report digital callback %m.port %m.enableDisable', 'reportDigital', '1', 'enable'],
	    	],
	    	'menus': {
	      		'pin': ['12', '2', '3'],
	      		'port': ['0', '1'],
	      		'dsetting': ['on', 'off'],
	      		'ppin': ['1', '2'],
				'io': ['output', 'input', 'pwm', 'analog'],
				'ioMode': ['digital', 'pwm'],
				'enableDisable': ['enable', 'disable']
	     	},
	    	url: 'http://www.warwick.ac.uk/tilesfortales'
	  	};
  	}

	ext._shutdown = function() {};

	ext.reportDigital = function(pin, setting) {
		console.log('reportDigital ' + pin + ' ' + setting);
		var bytearray = new Uint8Array(2);
		bytearray[0] = 0xD0 | pin; // report digital
		if('enable' == setting) {
			bytearray[1] = 1; // disable/enable
		}
		else {
			bytearray[1] = 0;
		}
		ext.socket.send(bytearray.buffer);
	}

  	ext.setPinMode = function(pin, mode) {
		if(ext.isConnected()) {
			var setting = 0x00;
		    console.log("setPinMode pin:" + pin + " mode:" + mode);
		    if('output' == mode) {
		    	setting = 0x01;
		    }
		    else if('analog' == mode) {
		    	setting = 0x02;
		    }
		    else if('pwm' == mode) {
		    	setting = 0x03;
		    }
		    var bytearray = new Uint8Array(3);
		    bytearray[0] = 0xF4;// PIN_MODE;
		    bytearray[1] = pin;
		    bytearray[2] = setting;
		    ext.socket.send(bytearray.buffer);
		}
  	}

	ext.isConnected = function() {
		var retval = false;
		if(ext.socket != null && ext.socket.readyState == ext.socket.OPEN) {
			retval = true;
		}
		return retval;
	}

	ext.disconnect = function() {
		if(ext.socket != null) {
			ext.socket.close();
			ext.socket = null;
		}
	}

	ext.onMessage = function(evt) {
		var dv = new DataView(evt.data);
		var r = "received message: ";
		for(var i=0; i<dv.byteLength; i++) {
			r = r + " " + dv.getUint8(i).toString(16);
		}

		if(dv.byteLength > 0) {
			if(dv.getUint8(0) == 0xF0) { // start sysex
				if(dv.getUint8(1) == 0x6E) { // pin state response
					// 2 will be pin number, 4 will be state
					var pin = dv.getUint8(2);
					var state = dv.getUint8(4);
					console.log("state " + state);
					console.log("state " + state >> 7);
					if(ext.messageQueue["pin-state-" + pin] != undefined) {
						console.log("got handler");
						ext.messageQueue["pin-state-" + pin](state);
					}
				}
			}
		}

		// 6e PIN_STATE_RESPONSE

		console.log(r);
	}

	ext.doSend = function(message) {
		console.log("SENT: " + message);
		ext.socket.send(message);
	}

	ext.onOpen = function(evt) {
		console.log("Connected");
		//ext.doSend("WebSocket rocks");
	}

	ext.onClose = function(evt) {
		console.log("onClose");
		ext.socket = null;
	}

	ext._getStatus = function() {
		var retval = {status: 1, msg: 'Not Connected'};
		if(ext.socket != null && ext.socket.readyState == ext.socket.OPEN) {
			retval = {status: 2, msg: 'Device connected'};
		}
		return retval;
	}

	ext.connect = function() {
		if(ext.socket == null) {
			ext.socket = new WebSocket("ws://" + ext.ip, 'firmata');
			ext.socket.binaryType = "arraybuffer";
			ext.socket.onopen = function(evt) {ext.onOpen(evt)};
			ext.socket.onmessage = function(evt) {ext.onMessage(evt)};
			ext.socket.onclose = function(evt) {ext.onClose(evt)};
		}
		else if(ext.socket.readyState == ext.socket.CLOSING || ext.socket.readyState == ext.socket.CLOSED) {
			ext.socket = null;
			ext.connect();
		}
		// if socket is in open or connecting state we're not going to do anything
	}

	ext.getPwm = function(pin) {
	};

	ext.setPwm = function(pin, setting) {
		var p = 4;
		if(pin == 2) {
			p = 5;
		}
		var url = ext.url + '/gpio' + p + '/' + setting;
		$.ajax({
			type: 'POST',
			url: url,
			success: function(response) {
			}
		});
		setTimeout(function(){ }, 10);
	}

  	ext.getDigital = function(pin, callback) {
  		if(ext.isConnected()) {
  			console.log("getDigital pin:" + pin);
	  		var bytearray = new Uint8Array(4);
	  		bytearray[0] = 0xF0; // Start sysex
	  		bytearray[1] = 0x6D; // PIN_STATE_QUERY
	  		bytearray[2] = pin;
	  		bytearray[3] = 0xF7; // end sysex
	  		ext.socket.send(bytearray);

			ext.messageQueue["pin-state-" + pin] = callback;
		}
	};

	ext.setDigital = function(pin, setting) {
		if(ext.isConnected()) {
			console.log("setDigital pin:" + pin + " setting:" + setting);
		    var s = 1;
		    if(setting == 'off') {
		      s = 0;
		    }
		    var bytearray = new Uint8Array(3);
		    bytearray[0] = 0xF5; // set digital pin value
		    bytearray[1] = pin;
		    bytearray[2] = s;

		    ext.socket.send(bytearray);
	    }
	}

	if(ext.name != undefined) {
		ScratchExtensions.register(ext.name, descriptor, ext);
	}
	else {
		ScratchExtensions.register(ext.ip, descriptor, ext);
	}

})();
<|endoftext|>