<commit_before>"use strict";

var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
var Promise = require('promise');
var DirectoryDb = require('./db.js');
var Session = require('./session.js');
var Account = require('./account.js');

var PROOF_LIFESPAN = 3600000; // ms = 1h

// db: either a String to specify the default database's directory location,
// or a constructor with the same format as specified in src/db.js.
function Registry(db) {
  if (Object(db) instanceof String) {
    this.db = new DirectoryDb({dir: db});
  } else {
    this.db = new db();
  }
}

Registry.prototype = {
  // The memory contains the absolute truth.
  // This also loads the associated account if found.
  // id: base64url session identifier
  // cb(error, session)
  load: function(id, cb) {
    cb = cb || function(){};
    var self = this;
    self.db.readSession(id, function(err, session) {
      if (err != null) { return cb(err); }
      if (session.emailVerified()) {
        self.db.readAccount(session.email, function(err, account) {
          session.account = account;
          cb(err, session);
        });
      } else {
        cb(null, session);
      }
    });
  },
  // email: account identifier, cb(error, Account)
  loadAccount: function(email, cb) {
    cb = cb || function(){};
    this.db.readAccount(email, cb);
  },
  // Destroy the session.
  // id: base64url session identifier
  // cb(error)
  logout: function(id, cb) {
    cb = cb || function(){};
    var self = this;
    self.load(id, function(err, session) {
      if (err != null) { return cb(err); }
      if (session.account != null) {
        session.account.rmSession(id);
        // Remember to save the modifications made to the account.
        var saveAccount = true;
      }
      self.db.deleteSession(id, function(err) {
        if (err != null) { return cb(err); }
        if (saveAccount) {
          self.db.updateAccount(session.account, cb);
        } else { cb(null); }
      });
    });
  },
  // Destroy the account and all associated sessions.
  // email: string
  // cb(error)
  rmAccount: function(email, cb) {
    cb = cb || function(){};
    var self = this;
    self.loadAccount(email, function(err, account) {
      if (err != null) { return cb(err); }
      var sessionDeleters = [];
      account.sessionIds.forEach(function(sessionId) {
        sessionDeleters.push(new Promise(function(resolve, reject) {
          self.db.deleteSession(sessionId, function(err) {
            if (err != null) { return reject(err); }
            resolve();
          });
        }));
      });
      Promise.all(sessionDeleters).then(function() {
        self.db.deleteAccount(email, function(err) {
          if (err != null) { return cb(err); }
          cb(null);
        });
      }).catch(function(err) {
        cb(err);
      });
    });
  },
  // Store the session data in the drive registry.
  // Also saves the session's account, if any.
  // session: a Session.
  save: function(session, cb) {
    cb = cb || function(){};
    var self = this;
    self.db.updateSession(session, function(err) {
      if (err != null) { return cb(err); }
      if (session.account != null) {
        self.db.updateAccount(session.account, cb);
      } else { cb(null); }
    });
  },
  // Add the session to the account, save the session and the account.
  // email, session, cb: function(err)
  addSessionToAccount: function(email, session, cb) {
    var self = this;
    self.loadAccount(email, function(err, account) {
      if (err != null) {
        var accountIsInexistent = (err.code === 'ENOENT');
        if (accountIsInexistent) {
          account = new Account(email, []);
        } else {
          return cb(err);
        }
      }
      account.addSession(session);
      // save() will save the account.
      session.account = account;
      self.save(session, cb);
    });
  },
  // cb(error)
  // Run once every time we start the software.
  setup: function(cb) {
    cb = cb || function(){};
    this.db.setup(cb);
  },
  // cb(err, secret, session)
  login: function(cb) {
    var session = Session.newSession();
    try {
      var secret = session.setToken();
    } catch(e) { return cb(e); }
    this.save(session, function(err) { cb(err, secret, session); });
  },
  // cb(err, secret, session)
  proof: function(id, email, cb) {
    var self = this;
    self.load(id, function(err, session) {
      if (err != null) { return cb(err); }
      try {
        var secret = session.setProof(email);
      } catch(e) { return cb(e); }
      self.save(session, function(err) { cb(err, secret, session); });
    });
  },
  // cb(err)
  manualConfirmEmail: function(id, email, cb) {
    var self = this;
    self.load(id, function(err, session) {
      if (err != null) { return cb(err); }
      session.email = email;
      session.proofHash = '';
      session.proofToken = '';
      session.proofCreatedAt = 0;
      // Saving is done inside.
      self.addSessionToAccount(email, session, cb);
    });
  },
  // Verify an email proof token in base64 by comparing it to the registry's.
  // cb(error, validity, session)
  confirm: function(id, token, cb) {
    var self = this;
    self.load(id, function(err, session) {
      if (err != null) { return cb(err); }
      try {
        var tokenBuf = new Buffer(token, 'base64');
      } catch(e) { return cb(e); }
      // Hash the token.
      if (!session.proofHash) {
        return cb(new Error('Cannot confirm this token'));
      }
      try {
        var hash = crypto.createHash(session.proofHash);
        hash.update(tokenBuf);
        var hashedToken = hash.digest('base64');
      } catch(e) { return cb(e); }
      // Check the validity.
      var inTime = ((Session.currentTime() - session.proofCreatedAt)
        < PROOF_LIFESPAN);
      var valid = (hashedToken === session.proofToken);
      // valid should be last, just in case short-circuit eval leaks data.
      var confirmed = inTime && valid;
      if (!confirmed) {
        return cb(null, false, session);
      }
      // The token is confirmed.
      session.proofHash = '';
      session.proofToken = '';
      session.proofCreatedAt = 0;
      // Saving is done inside.
      self.addSessionToAccount(session.email, session, function(err) {
        cb(err, true, session);
      });
    });
  },
  // Verify a token in base64 by comparing its hash to the registry's.
  // cb(err, authenticated, session)
  // authenticated should always be either true or false.
  auth: function(id, token, cb) {
    var self = this;
    this.load(id, function(err, session) {
      if (err != null) { return cb(err, false); }
      try {
        var tokenBuf = new Buffer(token, 'base64');
        // Hash the token.
        var hash = crypto.createHash(session.hash);
        hash.update(tokenBuf);
        var hashedToken = hash.digest('base64');
      } catch(e) {
        return cb(e, false);
      }
      var authenticated = (hashedToken === session.token);
      if (authenticated) {
        session.lastAuth = Session.currentTime();
      }
      cb(err, authenticated, session);
    });
  },
};

exports.Session = Session;
exports.Registry = Registry;
exports.base64url = Session.base64url;
exports.bufferFromBase64url = Session.bufferFromBase64url;
// Exports for the purpose of tests.
exports.currentTime = Session.currentTime;
exports.changeCurrentTime = Session.changeCurrentTime;
exports.PROOF_LIFESPAN = PROOF_LIFESPAN;
<commit_msg>Fix email confirmation with a dead token<commit_after>"use strict";

var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
var Promise = require('promise');
var DirectoryDb = require('./db.js');
var Session = require('./session.js');
var Account = require('./account.js');

var PROOF_LIFESPAN = 3600000; // ms = 1h

// db: either a String to specify the default database's directory location,
// or a constructor with the same format as specified in src/db.js.
function Registry(db) {
  if (Object(db) instanceof String) {
    this.db = new DirectoryDb({dir: db});
  } else {
    this.db = new db();
  }
}

Registry.prototype = {
  // The memory contains the absolute truth.
  // This also loads the associated account if found.
  // id: base64url session identifier
  // cb(error, session)
  load: function(id, cb) {
    cb = cb || function(){};
    var self = this;
    self.db.readSession(id, function(err, session) {
      if (err != null) { return cb(err); }
      if (session.emailVerified()) {
        self.db.readAccount(session.email, function(err, account) {
          session.account = account;
          cb(err, session);
        });
      } else {
        cb(null, session);
      }
    });
  },
  // email: account identifier, cb(error, Account)
  loadAccount: function(email, cb) {
    cb = cb || function(){};
    this.db.readAccount(email, cb);
  },
  // Destroy the session.
  // id: base64url session identifier
  // cb(error)
  logout: function(id, cb) {
    cb = cb || function(){};
    var self = this;
    self.load(id, function(err, session) {
      if (err != null) { return cb(err); }
      if (session.account != null) {
        session.account.rmSession(id);
        // Remember to save the modifications made to the account.
        var saveAccount = true;
      }
      self.db.deleteSession(id, function(err) {
        if (err != null) { return cb(err); }
        if (saveAccount) {
          self.db.updateAccount(session.account, cb);
        } else { cb(null); }
      });
    });
  },
  // Destroy the account and all associated sessions.
  // email: string
  // cb(error)
  rmAccount: function(email, cb) {
    cb = cb || function(){};
    var self = this;
    self.loadAccount(email, function(err, account) {
      if (err != null) { return cb(err); }
      var sessionDeleters = [];
      account.sessionIds.forEach(function(sessionId) {
        sessionDeleters.push(new Promise(function(resolve, reject) {
          self.db.deleteSession(sessionId, function(err) {
            if (err != null) { return reject(err); }
            resolve();
          });
        }));
      });
      Promise.all(sessionDeleters).then(function() {
        self.db.deleteAccount(email, function(err) {
          if (err != null) { return cb(err); }
          cb(null);
        });
      }).catch(function(err) {
        cb(err);
      });
    });
  },
  // Store the session data in the drive registry.
  // Also saves the session's account, if any.
  // session: a Session.
  save: function(session, cb) {
    cb = cb || function(){};
    var self = this;
    self.db.updateSession(session, function(err) {
      if (err != null) { return cb(err); }
      if (session.account != null) {
        self.db.updateAccount(session.account, cb);
      } else { cb(null); }
    });
  },
  // Add the session to the account, save the session and the account.
  // email, session, cb: function(err)
  addSessionToAccount: function(email, session, cb) {
    var self = this;
    self.loadAccount(email, function(err, account) {
      if (err != null) {
        var accountIsInexistent = (err.code === 'ENOENT');
        if (accountIsInexistent) {
          account = new Account(email, []);
        } else {
          return cb(err);
        }
      }
      account.addSession(session);
      // save() will save the account.
      session.account = account;
      self.save(session, cb);
    });
  },
  // cb(error)
  // Run once every time we start the software.
  setup: function(cb) {
    cb = cb || function(){};
    this.db.setup(cb);
  },
  // cb(err, secret, session)
  login: function(cb) {
    var session = Session.newSession();
    try {
      var secret = session.setToken();
    } catch(e) { return cb(e); }
    this.save(session, function(err) { cb(err, secret, session); });
  },
  // cb(err, secret, session)
  proof: function(id, email, cb) {
    var self = this;
    self.load(id, function(err, session) {
      if (err != null) { return cb(err); }
      try {
        var secret = session.setProof(email);
      } catch(e) { return cb(e); }
      self.save(session, function(err) { cb(err, secret, session); });
    });
  },
  // cb(err)
  manualConfirmEmail: function(id, email, cb) {
    var self = this;
    self.load(id, function(err, session) {
      if (err != null) { return cb(err); }
      session.email = email;
      session.proofHash = '';
      session.proofToken = '';
      session.proofCreatedAt = 0;
      // Saving is done inside.
      self.addSessionToAccount(email, session, cb);
    });
  },
  // Verify an email proof token in base64 by comparing it to the registry's.
  // cb(error, validity, session)
  confirm: function(id, token, cb) {
    var self = this;
    self.load(id, function(err, session) {
      if (err != null) { return cb(err); }
      try {
        var tokenBuf = new Buffer(token, 'base64');
      } catch(e) { return cb(e); }
      // Hash the token.
      if (!session.proofHash) {
        // The proof hash is inexistent, so the token is invalid.
        return cb(null, false, session);
      }
      try {
        var hash = crypto.createHash(session.proofHash);
        hash.update(tokenBuf);
        var hashedToken = hash.digest('base64');
      } catch(e) { return cb(e); }
      // Check the validity.
      var inTime = ((Session.currentTime() - session.proofCreatedAt)
        < PROOF_LIFESPAN);
      var valid = (hashedToken === session.proofToken);
      // valid should be last, just in case short-circuit eval leaks data.
      var confirmed = inTime && valid;
      if (!confirmed) {
        return cb(null, false, session);
      }
      // The token is confirmed.
      session.proofHash = '';
      session.proofToken = '';
      session.proofCreatedAt = 0;
      // Saving is done inside.
      self.addSessionToAccount(session.email, session, function(err) {
        cb(err, true, session);
      });
    });
  },
  // Verify a token in base64 by comparing its hash to the registry's.
  // cb(err, authenticated, session)
  // authenticated should always be either true or false.
  auth: function(id, token, cb) {
    var self = this;
    this.load(id, function(err, session) {
      if (err != null) { return cb(err, false); }
      try {
        var tokenBuf = new Buffer(token, 'base64');
        // Hash the token.
        var hash = crypto.createHash(session.hash);
        hash.update(tokenBuf);
        var hashedToken = hash.digest('base64');
      } catch(e) {
        return cb(e, false);
      }
      var authenticated = (hashedToken === session.token);
      if (authenticated) {
        session.lastAuth = Session.currentTime();
      }
      cb(err, authenticated, session);
    });
  },
};

exports.Session = Session;
exports.Registry = Registry;
exports.base64url = Session.base64url;
exports.bufferFromBase64url = Session.bufferFromBase64url;
// Exports for the purpose of tests.
exports.currentTime = Session.currentTime;
exports.changeCurrentTime = Session.changeCurrentTime;
exports.PROOF_LIFESPAN = PROOF_LIFESPAN;
<|endoftext|>


<commit_before>// Testing TODOs:
// - test extensively for missing optional parameters
// - test resumable without initial arg
// - test for correct context in callbacks
// - test that makePipeline is called with initial args
// - test that makePipeline passes intermediate results
// - test that makePipeline pass intial args to all intermediate functions
//
// Other TODOs:
// - replace `context` variable in resumable? Could just be called
//   using `call`
// - give resumable variadic initial args?
// - pass initial args to all functions in resumable?
// - get rid of options object for `repeater.retry`?
// - expose makePipeline

(function (env) {
	'use strict';

	var when = env.when || require('when');

	/**
	  * Convenience method for repeater.retry. It allows setup code, attempt
	  * code and success/error handling code to be specified in a flat object
	  * to achieve a "declarative" workflow.
	  *
	  * @param options object containing workflow functions. All options for
	  *     `repeater.retry` are allowed, in addition to the following options:
	  *     {
	  *         maxAttempts: (required) the maximum number of attempts to make.
	  *         before: (optional) function to execute one-time setup code
	  *         attempt: (required) function containing code to be retried
	  *             (becomes func parameter of `repeater.retry`).
	  *         validate: (optional) function containing validation code to
	  *             determine whether the resolution value of attempt is valid.
	  *         onSuccess: (optional) function to execute when the function
	  *             succeeds.
	  *         onFailure: (optional) function to execute when all retries are
	  *             exhausted and the function hasn't succeeded.
	  *         lastly: (optional) function to execute on success or failure.
	  *     }
	  * @return an automatically retrying function/method
	  */
	env.repeater = function(options) {
		return function () {
			var before = funcOrNull.call(this, options.before),
				attempt = funcOrNull.call(this, options.attempt),
				validate = funcOrNull.call(this, options.validate),
				onSuccess = funcOrNull.call(this, options.onSuccess),
				onError = funcOrNull.call(this, options.onError),
				lastly = funcOrNull.call(this, options.lastly),
				retrySeq = null,
				decoratedRetrySeq = null,
				mainSeq = null;

			retrySeq = makePipeline.call(this, [attempt, validate]);
			decoratedRetrySeq = env.repeater.retry.call(this, options.maxAttempts, retrySeq, options);
			mainSeq = makePipeline.call(this, [before, decoratedRetrySeq, onSuccess]);
			return mainSeq.apply(this, arguments).otherwise(onError).ensure(lastly);
		}
	}

	/**
	  * Function/method decorator for automatically retrying (a)synchronous
	  * functions a given number of times. All optional function arguments are
      * executed within the context the decorator is called.
	  *
	  * Return a function that, when called, stores the arguments to func,
	  * sets up the retry sequence, and returns a promise on the final
	  * result.
	  *
	  * @param maxAttempts the maximum number of times to try func. Can be a
	  *     function or method on the same object, but it will only be called
	  *     once per call of the decorated function.
	  * @param func the function to try
	  * @param options object containing optional parameters:
	  *     {
	  *         beforeRetry: function taking the value promised by func to be
	  *             called between retries. Could be used for logging,
	  *	            delaying, updating UI, etc. If an exception is thrown, the
	  *             rejection handler will itself reject instead of retrying
	  *             the func, and will proceed to the next attempt or final
	  *             failure.
	  *         provideAllErrors: if set to truthy, if all attempts are
	  *             exhausted without success, all rejection values are
	  *             returned in an array. Default behavior is to return only
	  *             the last rejection value.
	  *     }
	  * @return the decorated version of func
	  */
	env.repeater.retry = function(maxAttempts, func, options) {
		options = options || {};

		return function () {
			var hostObj = this, // context of func if it's a method
				beforeRetry = funcOrNull.call(hostObj, options.beforeRetry),
				funcArgs = arguments,
				chain = null,
				errorLog = [],
				succeeded = false,
				tries = callIfFunc.call(hostObj, maxAttempts),
				tryNumber = null;

			// Make the first attempt
			chain = promiseFunc();

			// Queue up handlers for retries
			for (tryNumber = 2; tryNumber <= tries; tryNumber++) {
				chain = chain.otherwise(retryFailureFilter);
			}

			// Add handler for final failure
			chain = chain.otherwise(finalRejctionFilter);

			return chain;

			function promiseFunc() {
				// Wrap func in a promise, even if it's synchronous
				try {
					return when(func.apply(hostObj, funcArgs));
				} catch (err) {
					return when.reject(err);
				}
			}

			function retryFailureFilter(err) {
				// Store the reject() arguments
				errorLog.push(err);

				// Resolve with the result of the retry
				// (promiseFunc ignores err if beforeRetry is not provided)
				return makePipeline.call(hostObj, [beforeRetry, promiseFunc])(err);
			}

			function finalRejctionFilter(val) {
				errorLog.push(val);

				// Forward a final rejected promise
				if (options.provideAllErrors) {
					throw errorLog;
				} else {
					throw errorLog[errorLog.length - 1];
				}
			}
		};
	};

	/**
	  * Promise compliant delay function. Can be wrapped in a function and used
	  * as a beforeRetry callback to space out attempts of a
	  * retry-wrapped task.
	  *
	  * @param ms the number of milliseconds to delay
	  * @param an optional function to call after the delay
	  * @return a promise that is resolved after a delay
	  */
	env.repeater.delay = function (ms, func) {
		return when.promise(function (resolve, reject) {
			setTimeout(function () {
				if (typeof func === 'function') {
					try {
						when(func.apply(null)).then(resolve, reject);
					} catch (err) {
						reject(err);
					}
				} else {
					resolve();
				}
			}, ms);
		});
	};

	/**
	  * Creates a resumable chain of possibly asynchronous functions. When the
	  * result is called, the functions are executed in order, with
	  * intermediate results passed down the line. If one of the functions
	  * rejects or throws an exception, the process halts. If the same object
	  * is called again, the sequence resumes from the failed function on. The
	  * argument for the failed function is automatically remembered.
	  *
	  * @param context the `this` context for the functions
	  * @param funcs an array of functions to call
	  * @param optional parameters
	  *     {
	  *         initialArg: (optional) argument for first function
	  *         onError: (optional) rejection filter if a function fails
	  *     }
	  * @return a function that starts/resumes the sequence, when called
	  */
	env.repeater.resumable = function (context, funcs, options) {
		// Start at the begining of the chain
		var progress = 0,
			lastResult;

		options = options || {};
		lastResult = options.initialArg;

		// When called, this queues up a sequence from wherever the last call
		// errored out
		return function () {
			var startFuncIndex = progress,
				chain = when.reduce(funcs.slice(progress), function (prevResult, nextFunc, index) {
					// index is 0 based, recover the absolute index
					var funcIndex = startFuncIndex + index;

					// Update our progress so we can resume if interrupted
					lastResult = prevResult;
					progress = funcIndex;

					return nextFunc.call(context, prevResult);
				}, lastResult)

			// Attach error handler
			chain = chain.otherwise(funcOrNull.call(context, options.onError));

			return chain;
		};
	};

	function funcOrNull(f) {
		var _this = this;
		return typeof f === 'function' ? function () { return f.apply(_this, arguments); } : null;
	}

	function callIfFunc(f) {
		return typeof f === 'function' ? f.apply(this, Array.prototype.slice.call(arguments, 1)) : f;
	}

	function makePipeline(ops) {
		var hostObj = this;

		// Returned function calls a series of functions, feeding the
		// arguments to the first, and intermediate results to the
		// subsequent functions
		return function () {
			var firstOp = true,
				initialArgs = Array.prototype.slice.call(arguments, 0);
			return when.reduce(ops, function (prevResult, nextOp, index) {
				if (nextOp) {
					if (firstOp) {
						firstOp = false;
						return nextOp.apply(hostObj, initialArgs);
					} else {
						// Send the intermediate result, plus initial args to subsequent functions
						return nextOp.apply(hostObj, [prevResult].concat(Array.prototype.slice.call(initialArgs, 0)));
					}
				} else {
					return prevResult;
				}
			}, {});	// Need to feed reduce an initial dummy value
		}
	}

	if (typeof module !== 'undefined') {
		module.exports = env.repeater;
	}
})(this);<commit_msg>remove out of date comments (preserved in working branch)<commit_after>(function (env) {
	'use strict';

	var when = env.when || require('when');

	/**
	  * Convenience method for repeater.retry. It allows setup code, attempt
	  * code and success/error handling code to be specified in a flat object
	  * to achieve a "declarative" workflow.
	  *
	  * @param options object containing workflow functions. All options for
	  *     `repeater.retry` are allowed, in addition to the following options:
	  *     {
	  *         maxAttempts: (required) the maximum number of attempts to make.
	  *         before: (optional) function to execute one-time setup code
	  *         attempt: (required) function containing code to be retried
	  *             (becomes func parameter of `repeater.retry`).
	  *         validate: (optional) function containing validation code to
	  *             determine whether the resolution value of attempt is valid.
	  *         onSuccess: (optional) function to execute when the function
	  *             succeeds.
	  *         onFailure: (optional) function to execute when all retries are
	  *             exhausted and the function hasn't succeeded.
	  *         lastly: (optional) function to execute on success or failure.
	  *     }
	  * @return an automatically retrying function/method
	  */
	env.repeater = function(options) {
		return function () {
			var before = funcOrNull.call(this, options.before),
				attempt = funcOrNull.call(this, options.attempt),
				validate = funcOrNull.call(this, options.validate),
				onSuccess = funcOrNull.call(this, options.onSuccess),
				onError = funcOrNull.call(this, options.onError),
				lastly = funcOrNull.call(this, options.lastly),
				retrySeq = null,
				decoratedRetrySeq = null,
				mainSeq = null;

			retrySeq = makePipeline.call(this, [attempt, validate]);
			decoratedRetrySeq = env.repeater.retry.call(this, options.maxAttempts, retrySeq, options);
			mainSeq = makePipeline.call(this, [before, decoratedRetrySeq, onSuccess]);
			return mainSeq.apply(this, arguments).otherwise(onError).ensure(lastly);
		}
	}

	/**
	  * Function/method decorator for automatically retrying (a)synchronous
	  * functions a given number of times. All optional function arguments are
      * executed within the context the decorator is called.
	  *
	  * Return a function that, when called, stores the arguments to func,
	  * sets up the retry sequence, and returns a promise on the final
	  * result.
	  *
	  * @param maxAttempts the maximum number of times to try func. Can be a
	  *     function or method on the same object, but it will only be called
	  *     once per call of the decorated function.
	  * @param func the function to try
	  * @param options object containing optional parameters:
	  *     {
	  *         beforeRetry: function taking the value promised by func to be
	  *             called between retries. Could be used for logging,
	  *	            delaying, updating UI, etc. If an exception is thrown, the
	  *             rejection handler will itself reject instead of retrying
	  *             the func, and will proceed to the next attempt or final
	  *             failure.
	  *         provideAllErrors: if set to truthy, if all attempts are
	  *             exhausted without success, all rejection values are
	  *             returned in an array. Default behavior is to return only
	  *             the last rejection value.
	  *     }
	  * @return the decorated version of func
	  */
	env.repeater.retry = function(maxAttempts, func, options) {
		options = options || {};

		return function () {
			var hostObj = this, // context of func if it's a method
				beforeRetry = funcOrNull.call(hostObj, options.beforeRetry),
				funcArgs = arguments,
				chain = null,
				errorLog = [],
				succeeded = false,
				tries = callIfFunc.call(hostObj, maxAttempts),
				tryNumber = null;

			// Make the first attempt
			chain = promiseFunc();

			// Queue up handlers for retries
			for (tryNumber = 2; tryNumber <= tries; tryNumber++) {
				chain = chain.otherwise(retryFailureFilter);
			}

			// Add handler for final failure
			chain = chain.otherwise(finalRejctionFilter);

			return chain;

			function promiseFunc() {
				// Wrap func in a promise, even if it's synchronous
				try {
					return when(func.apply(hostObj, funcArgs));
				} catch (err) {
					return when.reject(err);
				}
			}

			function retryFailureFilter(err) {
				// Store the reject() arguments
				errorLog.push(err);

				// Resolve with the result of the retry
				// (promiseFunc ignores err if beforeRetry is not provided)
				return makePipeline.call(hostObj, [beforeRetry, promiseFunc])(err);
			}

			function finalRejctionFilter(val) {
				errorLog.push(val);

				// Forward a final rejected promise
				if (options.provideAllErrors) {
					throw errorLog;
				} else {
					throw errorLog[errorLog.length - 1];
				}
			}
		};
	};

	/**
	  * Promise compliant delay function. Can be wrapped in a function and used
	  * as a beforeRetry callback to space out attempts of a
	  * retry-wrapped task.
	  *
	  * @param ms the number of milliseconds to delay
	  * @param an optional function to call after the delay
	  * @return a promise that is resolved after a delay
	  */
	env.repeater.delay = function (ms, func) {
		return when.promise(function (resolve, reject) {
			setTimeout(function () {
				if (typeof func === 'function') {
					try {
						when(func.apply(null)).then(resolve, reject);
					} catch (err) {
						reject(err);
					}
				} else {
					resolve();
				}
			}, ms);
		});
	};

	/**
	  * Creates a resumable chain of possibly asynchronous functions. When the
	  * result is called, the functions are executed in order, with
	  * intermediate results passed down the line. If one of the functions
	  * rejects or throws an exception, the process halts. If the same object
	  * is called again, the sequence resumes from the failed function on. The
	  * argument for the failed function is automatically remembered.
	  *
	  * @param context the `this` context for the functions
	  * @param funcs an array of functions to call
	  * @param optional parameters
	  *     {
	  *         initialArg: (optional) argument for first function
	  *         onError: (optional) rejection filter if a function fails
	  *     }
	  * @return a function that starts/resumes the sequence, when called
	  */
	env.repeater.resumable = function (context, funcs, options) {
		// Start at the begining of the chain
		var progress = 0,
			lastResult;

		options = options || {};
		lastResult = options.initialArg;

		// When called, this queues up a sequence from wherever the last call
		// errored out
		return function () {
			var startFuncIndex = progress,
				chain = when.reduce(funcs.slice(progress), function (prevResult, nextFunc, index) {
					// index is 0 based, recover the absolute index
					var funcIndex = startFuncIndex + index;

					// Update our progress so we can resume if interrupted
					lastResult = prevResult;
					progress = funcIndex;

					return nextFunc.call(context, prevResult);
				}, lastResult)

			// Attach error handler
			chain = chain.otherwise(funcOrNull.call(context, options.onError));

			return chain;
		};
	};

	function funcOrNull(f) {
		var _this = this;
		return typeof f === 'function' ? function () { return f.apply(_this, arguments); } : null;
	}

	function callIfFunc(f) {
		return typeof f === 'function' ? f.apply(this, Array.prototype.slice.call(arguments, 1)) : f;
	}

	function makePipeline(ops) {
		var hostObj = this;

		// Returned function calls a series of functions, feeding the
		// arguments to the first, and intermediate results to the
		// subsequent functions
		return function () {
			var firstOp = true,
				initialArgs = Array.prototype.slice.call(arguments, 0);
			return when.reduce(ops, function (prevResult, nextOp, index) {
				if (nextOp) {
					if (firstOp) {
						firstOp = false;
						return nextOp.apply(hostObj, initialArgs);
					} else {
						// Send the intermediate result, plus initial args to subsequent functions
						return nextOp.apply(hostObj, [prevResult].concat(Array.prototype.slice.call(initialArgs, 0)));
					}
				} else {
					return prevResult;
				}
			}, {});	// Need to feed reduce an initial dummy value
		}
	}

	if (typeof module !== 'undefined') {
		module.exports = env.repeater;
	}
})(this);<|endoftext|>


<commit_before>import Sequelize from 'sequelize';
import { GraphQLList } from 'graphql';
import _ from 'lodash';

module.exports = function (target, options) {
  var resolver
    , targetAttributes = target instanceof Sequelize.Model ? Object.keys(target.rawAttributes) : Object.keys(target.target.rawAttributes)
    , argsToFindOptions = function(args) {
        var result = {};

        if (args) {
          Object.keys(args).forEach(function (key) {
            if (~targetAttributes.indexOf(key)) {
              result.where = result.where || {};
              result.where[key] = args[key];
            }

            if (key === 'limit') {
              result.limit = args[key];
            }
          });
        }

        return result;
      };

  options = options || {};
  if (options.include === undefined) options.include = true;

  if (target instanceof Sequelize.Model) {

    resolver = function (source, args, root, ast, type) {
      var selections
        , attributes
        , include = []
        , list = type instanceof GraphQLList
        , findOptions = argsToFindOptions(args);

      type = type.ofType || type;

      selections = ast.selectionSet.selections.reduce(function (memo, selection) {
        memo[selection.name.value] = selection;
        return memo;
      }, {});

      attributes = Object.keys(selections)
                         .filter(attribute => ~targetAttributes.indexOf(attribute));

      if (!~attributes.indexOf(target.primaryKeyAttribute)) {
        attributes.push(target.primaryKeyAttribute);
      }

      Object.keys(selections).forEach(function (key) {
        var association
          , includeOptions
          , args;

        association = type._fields[key].resolve &&
                      type._fields[key].resolve.$association;

        if (association) {
          args = selections[key].arguments.reduce(function (memo, arg) {
            memo[arg.name.value] = arg.value.value;
            return memo;
          }, {});

          includeOptions = argsToFindOptions(args);

          if (options.include && !includeOptions.limit) {
            include.push(_.assign({association: association}, includeOptions));
          } else if (association.associationType === 'BelongsTo') {
            if (!~attributes.indexOf(association.foreignKey)) {
              attributes.push(association.foreignKey);
            }
          }
        }
      });

      findOptions.include = include;
      findOptions.attributes = attributes;

      return target[list ? 'findAll' : 'findOne'](findOptions);
    };
  }

  if (target instanceof require('sequelize/lib/associations/base')) {
    resolver = function (source, args, root, ast, type) {
      return source.get(target.as) || source[target.accessors.get](argsToFindOptions(args));
    };

    resolver.$association = target;
  }

  return resolver;
};
<commit_msg>pre-commit test<commit_after>import Sequelize from 'sequelize';
import { GraphQLList } from 'graphql';
import _ from 'lodash';

module.exports = function (target, options) {
  var resolver
    , targetAttributes = target instanceof Sequelize.Model ? Object.keys(target.rawAttributes) : Object.keys(target.target.rawAttributes)
    , argsToFindOptions = function(args) {
        var result = {};

        if (args) {
          Object.keys(args).forEach(function (key) {
            if (~targetAttributes.indexOf(key)) {
              result.where = result.where || {};
              result.where[key] = args[key];
            }

            if (key === 'limit') {
              result.limit = args[key];
            }
          });
        }

        return result;
      };

  options = options || {};
  if (options.include === undefined) options.include = true;

  if (target instanceof Sequelize.Model) {

    resolver = function (source, args, root, ast, type) {
      var selections
        , attributes
        , include = []
        , list = type instanceof GraphQLList
        , findOptions = argsToFindOptions(args);

      type = type.ofType || type;

      selections = ast.selectionSet.selections.reduce(function (memo, selection) {
        memo[selection.name.value] = selection;
        return memo;
      }, {});

      attributes = Object.keys(selections)
                         .filter(attribute => ~targetAttributes.indexOf(attribute));

      if (!~attributes.indexOf(target.primaryKeyAttribute)) {
        attributes.push(target.primaryKeyAttribute);
      }

      Object.keys(selections).forEach(function (key) {
        var association
          , includeOptions
          , args;

        association = type._fields[key].resolve &&
                      type._fields[key].resolve.$association;

        if (association) {
          args = selections[key].arguments.reduce(function (memo, arg) {
            memo[arg.name.value] = arg.value.value;
            return memo;
          }, {});

          includeOptions = argsToFindOptions(args);

          if (options.include && !includeOptions.limit) {
            include.push(_.assign({association: association}, includeOptions));
          } else if (association.associationType === 'BelongsTo') {
            if (!~attributes.indexOf(association.foreignKey)) {
              attributes.push(association.foreignKey);
            }
          }
        }
      });

      findOptions.include = include;
      findOptions.attributes = attributes;

      return target[list ? 'findAll' : 'findOne'](findOptions);
    };
  }

  if (target instanceof require('sequelize/lib/associations/base')) {
    resolver = function (source, args, root, ast, type) {
      return source.get(target.as) || source[target.accessors.get](argsToFindOptions(args));
    };

    resolver.$association = target;
  }

  return resolver;
};
<|endoftext|>


<commit_before>var res = {
    HelloWorld_png : "res/HelloWorld.png",
    CloseNormal_png : "res/CloseNormal.png",
    CloseSelected_png : "res/CloseSelected.png"
};

var g_resources = [];
for (var i in res) {
    g_resources.push(res[i]);
}<commit_msg>Basic set of resources for the main menu<commit_after>var res = {
    MenuBackground_png : "res/SpaceShooterRedux/Backgrounds/blue_480x320.png",

    GameBackground_png : "res/SpaceShooterRedux/Backgrounds/purple_960x320.png",

    // UI
    ButtonBlue_png : "res/SpaceShooterRedux/PNG/UI/buttonBlue.png",
    ButtonGreen_png : "res/SpaceShooterRedux/PNG/UI/buttonGreen.png",

    Kenvector_ttf : "res/SpaceShooterRedux/Bonus/kenvector_future.ttf",

};

// {fontName:"Kenvector", src:[{src:"res/SpaceShooterRedux/Bonus/kenvector_future.ttf", type:"truetype"}]}

var g_resources = [];
for (var i in res) {
    g_resources.push(res[i]);
}<|endoftext|>


<commit_before>
var EcoreFactory = Ecore.EcoreFactory;

Ecore.$ = root.jQuery || root.Zepto || root.ender || null;

var Ajax = {

    get: function(url, success, error) {
        if (!Ecore.$) return;

        return Ecore.$.ajax({
            url: url,
            dataType: 'json',
            success: success,
            error: error
        });
    },

    post: function(url, data, success, error) {
        if (!Ecore.$) return;

        return Ecore.$.ajax({
           type: 'POST',
           url: url,
           dataType: 'json',
           data: data,
           success: success,
           error: error
        });
    }

};

/**
 * JSON serializer and parser
 *
 * See https://github.com/ghillairet/emfjson for details
 * about the JSON format used for EMF Models.
 *
**/

Ecore.JSON = {

    parse: function(data) {
        var contents = [];

        function processFeature(object, eObject) {
            if (!object || !eObject)
                return function( feature ) {};

            return function( feature ) {
                var featureName = feature.get('name'),
                    value = object[featureName];

                if ( feature.isTypeOf('EAttribute') ) {
                    eObject.set( featureName, value );
                } else {
                    if (feature.get('upperBound') === 1) {
                        eObject.set( featureName, parseObject(value) );
                    } else {
                        if (value)
                            _.each(value, function(val) {
                                eObject.get( featureName ).add( parseObject(val) );
                            });
                    }
                }
            };
        }

        function parseObject(object) {
            if (object && object.eClass) {
                var eClass = Ecore.Registry.getEObject(object.eClass),
                    features = eClass.eAllStructuralFeatures(),
                    eObject = Ecore.create(eClass);

                _.each( features, processFeature(object, eObject) );

                return eObject;
            }

            return null;
        }

        contents.push( parseObject(data) );

        return contents;
    },

    toJSON: function(model) {
        var contents = model.contents,
            indexes = {};
            indexes[model.uri] = buildIndex(model);

        function uri(owner, value) {
            var valueModel = value.eResource(),
                ownerModel = owner.eResource(),
                external = valueModel !== ownerModel;

            if (!valueModel || !ownerModel) return;
            if (!indexes[valueModel.uri]) {
                indexes[valueModel.uri] = buildIndex(valueModel);
            }

            var index = indexes[valueModel.uri];
            for (var key in index) {
                if (index[key] === value) {
                    return external ? valueModel.uri + '#' + key : key;
                }
            }

            return null;
        }

        function processFeature( object, data ) {
            if (!object || !data) return function() {};

            return function( feature ) {
                var featureName = feature.get('name');

                if (!object.isSet(featureName)) return;

                var value = object.get(featureName),
                    isMany = feature.get('upperBound') !== 1;

                if (feature.isTypeOf('EAttribute')) {
                    data[featureName] = value;
                } else {
                    if (feature.get('isContainment')) {
                        if (isMany) {
                            data[featureName] = [];
                            value.each(function(val) {
                                data[featureName].push( jsonObject(val) );
                            });
                        } else {
                            data[featureName] = jsonObject(value);
                        }
                    } else {
                        if (isMany) {
                            data[featureName] = [];
                            value.each(function(val) {
                               data[featureName].push({
                                   '$ref': uri(object, val),
                                   'eClass': val.eClass.eURI()
                               });
                            });
                        } else {
                            data[featureName] = {
                                '$ref': uri(object, value),
                                'eClass': value.eClass.eURI()
                            };
                        }
                    }
                }
            };
        }

        function jsonObject(object) {
            var eClass = object.eClass,
                features = eClass.eAllStructuralFeatures(),
                data = {eClass: eClass.eURI()};

            _.each( features, processFeature(object, data) );

            return data;
        }

        var data;
        if (contents.length === 1) {
            var eObject = contents[0];
            data = jsonObject(eObject);
        }
        return data;
    }

};

// Resource
//
var Resource = Ecore.Resource = function(uri) {
    this.uri = uri;
    this.contents = [];

    return this;
};

Resource.prototype = {

    clear: function() {
        this.contents.length = 0;
        return this;
    },

    add: function(eObject) {
        if (eObject) {
            eObject.eContainer = this;
            this.contents.push(eObject);
        }

        return this;
    },

    addAll: function(content) {
        if (_.isArray(content)) {
            _.each(content, function(eObject) {
                this.add(eObject);
            }, this);
        }

        return this;
    },

    getEObject: function(fragment) {
        if (!fragment) return null;

        return buildIndex(this)[fragment];
    },

    each: function(iterator, context) {
        return _.each(this.contents, iterator, context);
    },

    toJSON: function() {
        return Ecore.JSON.toJSON(this);
    },

    save: function(success, error) {
        var data = this.toJSON();
        if (data) {
            Ajax.post(this.uri, data, success, error);
        }
    },

    load: function(success, error, data) {
        var model = this;
        var loadSuccess = function(data) {
            var content = Ecore.JSON.parse(data);
            model.addAll(content);
            return success(model);
        };

        if (data) {
            return loadSuccess(data);
        } else {
            return Ajax.get(this.uri, loadSuccess, error);
        }
    }
};

function initEcoreModel() {
    var model = new Resource('http://www.eclipse.org/emf/2002/Ecore');
    model.add(Ecore.EcorePackage);

    Ecore.Registry.register(model);

    return model;
}

var Registry = function() {
    var instance;

    Registry = function Registry() {
        return instance;
    };

    Registry.prototype = this;

    instance = new Registry();

    instance.constructor = Registry;
    instance.models = {};

    return instance;
};

Registry.prototype = {

    register: function(model) {
        this.models[model.uri] = model;

        return this;
    },

    getEObject: function(uri) {
        var split = uri.split('#');
        var base = split[0];
        var fragment;
        if (split.length === 2) {
            fragment = split[1];
        }

        var model = this.models[base];
        if (model && fragment) {
            var index = buildIndex(model);
            var found = index[fragment];
            return found;
        }

        return null;
    }

};

Ecore.Registry = new Registry();
initEcoreModel();

function buildIndex(model) {
    var base = model.uri,
        index = {},
        contents = model.contents;

    if (contents.length === 1) {
        var root = contents[0];

        function _buildIndex(object, idx) {
            var eClass = object.eClass,
                eFeatures = eClass.eAllStructuralFeatures();
            index[idx] = object;

            _.each(eFeatures, function(feature) {
                var value = object.get(feature.get('name'));

                if (value && feature.isTypeOf('EReference') && feature.get('isContainment')) {

                    if (feature.get('upperBound') === 1) {

                        var _idx = value.eURIFragmentSegment(feature, idx, -1);
                        _buildIndex(value, _idx);

                    } else {

                        value.each(function(val) {
                            var position = value.indexOf( val );
                            var _idx = val.eURIFragmentSegment(feature, idx, position);
                            _buildIndex(val, _idx);
                        });

                    }

                }
            });
        }

        var iD = root.eClass.get('eIDAttribute') || null;
        if (iD) {
            _buildIndex(root, root.get(iD.name));
        } else {
            _buildIndex(root, '/');
        }
    }

    return index;
}

})();
<commit_msg>Add resolution of references during JSON parsing.<commit_after>
var EcoreFactory = Ecore.EcoreFactory;

Ecore.$ = root.jQuery || root.Zepto || root.ender || null;

var Ajax = {

    get: function(url, success, error) {
        if (!Ecore.$) return;

        return Ecore.$.ajax({
            url: url,
            dataType: 'json',
            success: success,
            error: error
        });
    },

    post: function(url, data, success, error) {
        if (!Ecore.$) return;

        return Ecore.$.ajax({
           type: 'POST',
           url: url,
           dataType: 'json',
           data: data,
           success: success,
           error: error
        });
    }

};

/**
 * JSON serializer and parser
 *
 * See https://github.com/ghillairet/emfjson for details
 * about the JSON format used for EMF Models.
 *
**/

Ecore.JSON = {

    parse: function(model, data) {
        var toResolve = [];

        function processFeature(object, eObject) {
            if (!object || !eObject)
                return function( feature ) {};

            return function( feature ) {
                var featureName = feature.get('name'),
                    value = object[featureName];

                if (typeof value !== 'undefined') {
                    if ( feature.isTypeOf('EAttribute') ) {
                        eObject.set( featureName, value );
                    } else if (feature.get('isContainment')) {
                        if (feature.get('upperBound') === 1) {
                            eObject.set( featureName, parseObject(value) );
                        } else {
                            _.each(value || [], function(val) {
                                eObject.get( featureName ).add( parseObject(val) );
                            });
                        }
                    } else {
                        toResolve.push({ parent: eObject, feature: feature, value: value });
                    }
                }
            };
        }

        function isLocal(uri) {
            return uri.substring(0, 1) === '/';
        }

        function resolveReferences() {
            var index = buildIndex(model);

            function setReference(parent, feature, value, isMany) {
                var ref = value.$ref,
                    resolved;

                if (isLocal(ref)) {
                    resolved = index[ref];
                } else {
                    resolved = Ecore.Registry.getEObject(ref);
                }

                if (resolved) {
                    if (isMany) {
                        parent.get(feature.get('name')).add(resolved);
                    } else {
                        parent.set(feature.get('name'), resolved);
                    }
                }
            }

            _.each(toResolve, function(resolving) {
                var parent = resolving.parent,
                    feature = resolving.feature,
                    value = resolving.value;

                if (feature.get('upperBound') === 1) {
                    setReference(parent, feature, value, false);
                } else {
                    _.each(value, function(val) {
                        setReference(parent, feature, val, true);
                    });
                }
            });
        }

        function parseObject(object) {
            var eObject;

            if (object && object.eClass) {
                var eClass = Ecore.Registry.getEObject(object.eClass),
                    features = eClass.eAllStructuralFeatures();

                eObject = Ecore.create(eClass);

                _.each( features, processFeature(object, eObject) );
            }

            return eObject;
        }

        var parsed = parseObject(data);
        if (parsed) {
            model.add(parsed);
            resolveReferences();
        }
    },

    toJSON: function(model) {
        var contents = model.contents,
            indexes = {};
            indexes[model.uri] = buildIndex(model);

        function uri(owner, value) {
            var valueModel = value.eResource(),
                ownerModel = owner.eResource(),
                external = valueModel !== ownerModel;

            if (!valueModel || !ownerModel) return;
            if (!indexes[valueModel.uri]) {
                indexes[valueModel.uri] = buildIndex(valueModel);
            }

            var index = indexes[valueModel.uri];
            for (var key in index) {
                if (index[key] === value) {
                    return external ? valueModel.uri + '#' + key : key;
                }
            }

            return null;
        }

        function processFeature( object, data ) {
            if (!object || !data) return function() {};

            return function( feature ) {
                var featureName = feature.get('name');

                if (!object.isSet(featureName)) return;

                var value = object.get(featureName),
                    isMany = feature.get('upperBound') !== 1;

                if (feature.isTypeOf('EAttribute')) {
                    data[featureName] = value;
                } else {
                    if (feature.get('isContainment')) {
                        if (isMany) {
                            data[featureName] = [];
                            value.each(function(val) {
                                data[featureName].push( jsonObject(val) );
                            });
                        } else {
                            data[featureName] = jsonObject(value);
                        }
                    } else {
                        if (isMany) {
                            data[featureName] = [];
                            value.each(function(val) {
                               data[featureName].push({
                                   '$ref': uri(object, val),
                                   'eClass': val.eClass.eURI()
                               });
                            });
                        } else {
                            data[featureName] = {
                                '$ref': uri(object, value),
                                'eClass': value.eClass.eURI()
                            };
                        }
                    }
                }
            };
        }

        function jsonObject(object) {
            var eClass = object.eClass,
                features = eClass.eAllStructuralFeatures(),
                data = {eClass: eClass.eURI()};

            _.each( features, processFeature(object, data) );

            return data;
        }

        var data;
        if (contents.length === 1) {
            var eObject = contents[0];
            data = jsonObject(eObject);
        }
        return data;
    }

};

// Resource
//
var Resource = Ecore.Resource = function(uri) {
    this.uri = uri;
    this.contents = [];

    return this;
};

Resource.prototype = {

    clear: function() {
        this.contents.length = 0;
        return this;
    },

    add: function(eObject) {
        if (eObject) {
            eObject.eContainer = this;
            this.contents.push(eObject);
        }

        return this;
    },

    addAll: function(content) {
        if (_.isArray(content)) {
            _.each(content, function(eObject) {
                this.add(eObject);
            }, this);
        }

        return this;
    },

    getEObject: function(fragment) {
        if (!fragment) return null;

        return buildIndex(this)[fragment];
    },

    each: function(iterator, context) {
        return _.each(this.contents, iterator, context);
    },

    toJSON: function() {
        return Ecore.JSON.toJSON(this);
    },

    parse: function(data) {
        Ecore.JSON.parse(this, data);
        return this;
    },

    save: function(success, error) {
        var data = this.toJSON();
        if (data) {
            Ajax.post(this.uri, data, success, error);
        }
    },

    load: function(success, error, data) {
        var model = this;
        var loadSuccess = function(data) {
            model.parse(data);
            return success(model);
        };

        if (data) {
            return loadSuccess(data);
        } else {
            return Ajax.get(this.uri, loadSuccess, error);
        }
    }
};

function initEcoreModel() {
    var model = new Resource('http://www.eclipse.org/emf/2002/Ecore');
    model.add(Ecore.EcorePackage);

    Ecore.Registry.register(model);

    return model;
}

var Registry = function() {
    var instance;

    Registry = function Registry() {
        return instance;
    };

    Registry.prototype = this;

    instance = new Registry();

    instance.constructor = Registry;
    instance.models = {};

    return instance;
};

Registry.prototype = {

    /**
     * Registers a model to the Registry by it's URI.
     *
    **/

    register: function(model) {
        this.models[model.uri] = model;

        return this;
    },

    /**
     * Returns the EObject corresponding to the given URI.
     *
    **/

    getEObject: function(uri) {
        var split = uri.split('#'),
            base = split[0],
            model = this.models[base],
            fragment,
            eObject;

        if (split.length === 2) {
            fragment = split[1];
        }

        if (model && fragment) {
            var index = buildIndex(model);
            eObject = index[fragment];
        }

        return eObject;
    }

};

Ecore.Registry = new Registry();
initEcoreModel();

function buildIndex(model) {
    var base = model.uri,
        index = {},
        contents = model.contents;

    if (contents.length === 1) {
        var root = contents[0];

        function _buildIndex(object, idx) {
            var eClass = object.eClass,
                eFeatures = eClass.eAllStructuralFeatures();
            index[idx] = object;

            _.each(eFeatures, function(feature) {
                var value = object.get(feature.get('name'));

                if (value && feature.isTypeOf('EReference') && feature.get('isContainment')) {
                    if (feature.get('upperBound') === 1) {
                        var _idx = value.fragment();
                        _buildIndex(value, _idx);
                    } else {
                        value.each(function(val) {
                            var position = value.indexOf( val );
                            var _idx = val.fragment();
                            _buildIndex(val, _idx);
                        });
                    }
                }
            });
        }

        var iD = root.eClass.get('eIDAttribute') || null;
        if (iD) {
            _buildIndex(root, root.get(iD.name));
        } else {
            _buildIndex(root, '/');
        }
    }

    return index;
}

})();
<|endoftext|>


<commit_before>/* global app, Backbone, _ */
/**
 * ChatApp models and collections.
 */
(function(app, Backbone) {
  "use strict";

  /**
   * FileTransfer model.
   *
   * Attributes:
   * - {Integer} progress
   *
   * Fired when a new chunk is available.
   * @event chunk
   * @param {String} id the id of the file transfer
   * @param {ArrayBuffer} chunk
   *
   * Fired when the transfer is complete.
   * @event complete
   * @param {File|Blob} file the file transfered
   *
   * Example:
   *
   * // Sender side
   * var transfer =
   *   new app.models.FileTransfer({file: file}, {chunkSize: 512 * 1024});
   * transfer.on("chunk", function(id, chunk) {
   *   sendChunk(id, chunk);
   * });
   * transfer.start();
   *
   * // Receiver side
   * var transfer =
   *   new app.models.FileTransfer({filename: filename, size: size});
   * transfer.on("complete", function(blob) {
   *   window.URL.createObjectURL(blob);
   * });
   * transfer.append(chunk);
   * transfer.append(chunk);
   * transfer.append(chunk);
   * ...
   *
   */
  app.models.FileTransfer = Backbone.Model.extend({

    /**
     * Filetransfer model constructor.
     * @param  {Object}  attributes  Model attributes
     * @param  {Object}  options     Model options
     *
     * Attribues:
     *
     * When initiating a file tranfer
     *
     * - {File} file The file to transfer
     *
     * When receiving a file transfer
     *
     * - {String} filename The name of the received file
     * - {Integer} size The size of the received file
     *
     * Options:
     *
     * When initiating a file tranfer
     *
     * - {Integer} chunkSize The size of the chunks
     *
     */
    initialize: function(attributes, options) {
      this.options = options;
      this.id = this.set("id", _.uniqueId()).id;

      if (attributes.file) {
        this.file          = attributes.file;
        this.filename      = attributes.file.name;
        this.size          = attributes.file.size;
        this.reader        = new FileReader();
        this.reader.onload = this._onChunk.bind(this);
      } else {
        this.size          = attributes.size;
        this.filename      = attributes.filename;
        this.chunks        = [];
      }

      this.seek = 0;
      this.on("chunk", this._onProgress, this);
    },

    /**
     * Turns a FileTransfer object into a JSON ready object.
     *
     * @return {Object} the serializable object
     *
     * Return value:
     * - {String} filename The name of the file
     * - {Integer} progress The progress of the file transfer
     */
    toJSON: function() {
      var progress = this.get("progress");
      var json = {
        filename: this.filename,
        progress: progress || 0
      };

      if (progress === 100)
        json.url = window.URL.createObjectURL(this.blob || this.file);

      return json;
    },

    /**
     * Start the file transfer.
     *
     * It actually trigger the file transfer to emit chunks one after
     * the other until the end of the file is reached.
     */
    start: function() {
      this._readChunk();
    },

    /**
     * Append a chunk to the current file transfer.
     *
     * Accumulates the data until the transfer is complete.
     * Raise an error if we append more data than expected.
     *
     * @param {ArrayBuffer} chunk the chunk to append
     */
    append: function(chunk) {
      this.chunks.push(chunk);
      this.seek += chunk.byteLength;

      if (this.seek === this.size) {
        this.blob = new Blob(this.chunks);
        this.trigger("complete", this.blob);
      }
      this.trigger("chunk", this.id, chunk);

      if (this.seek > this.size)
        throw new Error("Received more data than expected: " +
                        this.seek + " instead of " + this.size);
    },

    _onChunk: function(event) {
      var data = event.target.result;

      this.seek += data.byteLength;
      this.trigger("chunk", this.id, data);

      if (this.seek < this.file.size)
        this._readChunk();
      else
        this.trigger("complete", this.file);
    },

    _onProgress: function() {
      var progress = Math.floor(this.seek * 100 / this.size);
      this.set("progress", progress);
    },

    _readChunk: function() {
      var blob = this.file.slice(this.seek, this.seek + this.options.chunkSize);
      this.reader.readAsArrayBuffer(blob);
    }
  });
})(app, Backbone);

<commit_msg>FileTransfer: escape the filename<commit_after>/* global app, Backbone, _ */
/**
 * ChatApp models and collections.
 */
(function(app, Backbone) {
  "use strict";

  /**
   * FileTransfer model.
   *
   * Attributes:
   * - {Integer} progress
   *
   * Fired when a new chunk is available.
   * @event chunk
   * @param {String} id the id of the file transfer
   * @param {ArrayBuffer} chunk
   *
   * Fired when the transfer is complete.
   * @event complete
   * @param {File|Blob} file the file transfered
   *
   * Example:
   *
   * // Sender side
   * var transfer =
   *   new app.models.FileTransfer({file: file}, {chunkSize: 512 * 1024});
   * transfer.on("chunk", function(id, chunk) {
   *   sendChunk(id, chunk);
   * });
   * transfer.start();
   *
   * // Receiver side
   * var transfer =
   *   new app.models.FileTransfer({filename: filename, size: size});
   * transfer.on("complete", function(blob) {
   *   window.URL.createObjectURL(blob);
   * });
   * transfer.append(chunk);
   * transfer.append(chunk);
   * transfer.append(chunk);
   * ...
   *
   */
  app.models.FileTransfer = Backbone.Model.extend({

    /**
     * Filetransfer model constructor.
     * @param  {Object}  attributes  Model attributes
     * @param  {Object}  options     Model options
     *
     * Attribues:
     *
     * When initiating a file tranfer
     *
     * - {File} file The file to transfer
     *
     * When receiving a file transfer
     *
     * - {String} filename The name of the received file
     * - {Integer} size The size of the received file
     *
     * Options:
     *
     * When initiating a file tranfer
     *
     * - {Integer} chunkSize The size of the chunks
     *
     */
    initialize: function(attributes, options) {
      this.options = options;
      this.id = this.set("id", _.uniqueId()).id;

      if (attributes.file) {
        this.file          = attributes.file;
        this.filename      = attributes.file.name;
        this.size          = attributes.file.size;
        this.reader        = new FileReader();
        this.reader.onload = this._onChunk.bind(this);
      } else {
        this.size          = attributes.size;
        this.filename      = attributes.filename;
        this.chunks        = [];
      }

      this.seek = 0;
      this.on("chunk", this._onProgress, this);
    },

    /**
     * Turns a FileTransfer object into a JSON ready object.
     *
     * @return {Object} the serializable object
     *
     * Return value:
     * - {String} filename The name of the file
     * - {Integer} progress The progress of the file transfer
     */
    toJSON: function() {
      var progress = this.get("progress");
      var json = {
        filename: _.escape(this.filename),
        progress: progress || 0
      };

      if (progress === 100)
        json.url = window.URL.createObjectURL(this.blob || this.file);

      return json;
    },

    /**
     * Start the file transfer.
     *
     * It actually trigger the file transfer to emit chunks one after
     * the other until the end of the file is reached.
     */
    start: function() {
      this._readChunk();
    },

    /**
     * Append a chunk to the current file transfer.
     *
     * Accumulates the data until the transfer is complete.
     * Raise an error if we append more data than expected.
     *
     * @param {ArrayBuffer} chunk the chunk to append
     */
    append: function(chunk) {
      this.chunks.push(chunk);
      this.seek += chunk.byteLength;

      if (this.seek === this.size) {
        this.blob = new Blob(this.chunks);
        this.trigger("complete", this.blob);
      }
      this.trigger("chunk", this.id, chunk);

      if (this.seek > this.size)
        throw new Error("Received more data than expected: " +
                        this.seek + " instead of " + this.size);
    },

    _onChunk: function(event) {
      var data = event.target.result;

      this.seek += data.byteLength;
      this.trigger("chunk", this.id, data);

      if (this.seek < this.file.size)
        this._readChunk();
      else
        this.trigger("complete", this.file);
    },

    _onProgress: function() {
      var progress = Math.floor(this.seek * 100 / this.size);
      this.set("progress", progress);
    },

    _readChunk: function() {
      var blob = this.file.slice(this.seek, this.seek + this.options.chunkSize);
      this.reader.readAsArrayBuffer(blob);
    }
  });
})(app, Backbone);

<|endoftext|>


<commit_before>require.config({
    baseUrl: '/static/js/',
    paths: {
        jquery: 'libs/jquery-1.11.1.min',
        bootstrap: 'libs/bootstrap.min',
        jqueryui: 'libs/jquery-ui.min',
        session_security: 'session_security',
        underscore: 'libs/underscore-min',
        assetscore: 'libs/assets.core',
        assetsresponsive: 'libs/assets.responsive',
        base: 'base',
        bloodhound: 'libs/bloodhound',
        typeahead : 'libs/typeahead',
        tokenfield: 'libs/bootstrap-tokenfield.min'
    },
    shim: {
        'bootstrap': ['jquery'],
        'jqueryui': ['jquery'],
        'session_security': ['jquery'],
        'assetscore': ['jquery', 'bootstrap', 'jqueryui'],
        'assetsresponsive': ['jquery', 'bootstrap', 'jqueryui'],
        'underscore': {exports: '_'},
        'tokenfield': ['jquery', 'jqueryui'],
        'typeahead':{
            deps: ['jquery'],
            init: function ($) {
                return require.s.contexts._.registry['typeahead.js'].factory( $ );
            }
        },
        'bloodhound': {
           deps: ['jquery'],
           exports: 'Bloodhound'
        }
    }
});

require([
    'jquery',
    'jqueryui',
    'bootstrap',
    'session_security',
    'bloodhound',
    'typeahead',
    'underscore',
    'assetscore',
    'assetsresponsive',
    'base',
    'tokenfield'
], function($, jqueryui, bootstrap, session_security, Bloodhound, typeahead, _) {
    'use strict';

    var geneListField = $('#paste-in-genes');
    var geneFavs = (gene_fav) ? gene_fav.genes : [];

    if(typeof(uploaded_genes)!== 'undefined' && uploaded_genes.length > 0 ){
        geneFavs = geneFavs.concat(uploaded_genes);
    }

    //create bloodhound typeahead engine for gene suggestions
    var gene_suggestions = new Bloodhound({
        datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
        queryTokenizer: Bloodhound.tokenizers.whitespace,
        prefetch : base_url + '/genes/suggest/a.json',
        remote: {
            url: base_url + '/genes/suggest/%QUERY.json',
            wildcard: '%QUERY'
        }
    });
    gene_suggestions.initialize();
    function createTokenizer() {
        // be aware bootstrap tokenfield requires 'value' as the datem attribute field : https://github.com/sliptree/bootstrap-tokenfield/issues/189
        geneListField.tokenfield({
            typeahead : [
                null, {
                    source: gene_suggestions.ttAdapter(),
                    display: 'value'
                }
            ],
            delimiter : " ",
            minLength: 2,
            tokens: geneFavs
        }).on('tokenfield:createdtoken', function (event) {
            //  Check whether the user enter a repetitive token
            //  If it is a repetitive token, show a message instead
            var existingGenes = event.currentTarget.value.split(' ');
            var parentHolder = $('#tokenfield-holder');
            $.each(existingGenes, function (index, gene) {
                if (gene.toUpperCase() === event.attrs.value.toUpperCase()) {
                    $(event.relatedTarget).addClass('invalid repeat');
                    $('.helper-text__repeat').show();
                }
            });

            //  check whether user enter a valid gene name
            //var isValid = true;
            validate_genes([event.attrs.value], function validCallback(result){
                if(!result[event.attrs.value]){
                    $(event.relatedTarget).addClass('invalid error');
                    $('.helper-text__invalid').show();
                }
                if ($('div.token.invalid.error').length < 1) {
                    $('.helper-text__invalid').hide();
                }
                if ($('div.token.invalid.repeat').length < 1) {
                    $('.helper-text__repeat').hide();
                }
            })
        }).on('tokenfield:removedtoken', function (event) {
            if ($('div.token.invalid.error').length < 1) {
                $('.helper-text__invalid').hide();
            }
            if ($('div.token.invalid.repeat').length < 1) {
                $('.helper-text__repeat').hide();
            }
        }).on('tokenfield:edittoken',function(e){
            e.preventDefault();
            return false;
        });
    }
    createTokenizer();

    // Clear all entered genes list on click
    $('#clearAll').on('click', function (event) {
        if($('.tokenfield').hasClass('focus')){
            // the tokenfield takes enter key and click event, and it will trigger the clear all method,
            // this code checks whether tokenfield is on focus, if yes, it preventDefault and do nothing
            event.preventDefault();
        }else{
            geneFavs = [];
            geneListField.tokenfield('setTokens', []);
            geneListField.tokenfield('createToken' , 'this is an arbitrary token to get bootstrap to clear the existing tokens');
            geneListField.tokenfield('setTokens', []);
        }
        return false;
    });

    // Genes upload page, file upload button onclick
    // Check if the browser support formdata for file upload
    var xhr2 = !! ( window.FormData && ("upload" in ($.ajaxSettings.xhr())  ));
    var uploaded_list;
    if(!xhr2 || !window.File || !window.FileReader || !window.FileList || !window.Blob){
        var errorMsg = 'Your browser doesn\'t support file upload. You can paste your gene list from the option below.';
        $('#file-upload-btn').attr('disabled', true).parent()
                             .append('
' + errorMsg + '
');
    }else{
        // If file upload is supported
        var fileUploadField = $('#file-upload-field');
        var validFileTypes = ['txt', 'csv'];

        $('#file-upload-btn').click(function(event){
            event.preventDefault()
            fileUploadField.click();
        });
        fileUploadField.on('change', function(event){
            var file = this.files[0];
            var ext = file.name.split(".").pop().toLowerCase();

            // check file format against valid file types
            if(validFileTypes.indexOf(ext) < 0){
                // If file type is not correct
                alert('Please upload a .txt or .csv file');
                return false;
            } else{
                $('#selected-file-name').text(file.name);
                $('#uploading').addClass('in');
                $('#file-upload-btn').hide();
            }

            if(event.target.files != undefined){
                var fr = new FileReader();
                var uploaded_gene_list;
                fr.onload = function(event){
                    uploaded_gene_list = fr.result.split(/[ \(,\)]+/).filter(Boolean);

                    // Send the uploaded gene's list to the backend
                    uploaded_list = checkUploadedGeneListAgainstGeneIdentifier(uploaded_gene_list);
                    for(var i in uploaded_list.valid){
                         geneListField.tokenfield('createToken', uploaded_list.valid[i]);
                    }
                    for(var i in uploaded_list.invalid){
                         geneListField.tokenfield('createToken', uploaded_list.invalid[i]);
                    }

                    $('#uploading').removeClass('in');
                    $('#file-upload-btn').show();
                }
                fr.readAsText(event.target.files.item(0));
            }
        })
    }

    //$('#paste-upload').on('click', function(){
    //    var pasted_genes = $($(this).data('target')).val().split(/[ ,]+/).filter(Boolean);
    //
    //    uploaded_list = checkUploadedGeneListAgainstGeneIdentifier(pasted_genes);
    //    console.log(pasted_genes);
    //})

    //$('#clearAll').on('click', function(){
    //    $('#paste-in-genes').val('');
    //})
    //$('#upload-without-error-genes').on('click', function(){
    //    // upload valid gene list
    //    // uploaded_list.valid
    //    console.log(uploaded_list.valid);
    //})
    //$('#upload-with-error-genes').on('click', function(){
    //    var newlist = $($(this).data('target')).val().split(/[ ,]+/).filter(Boolean);
    //    newlist = uploaded_list.valid.concat(newlist);
    //
    //    // Upload newlist
    //    console.log(uploaded_list.valid);
    //})

    $('form.create-gene-list').on('submit', function() {
        $(this).find('input[type="submit"]').attr('disabled', 'disabled');
    });

    /*
        Used for getting the CORS token for submitting data
     */
    function get_cookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }

    function validate_genes(list, cb){
        if(list.length > 0){
            var csrftoken = get_cookie('csrftoken');
            $.ajax({
                type        : 'POST',
                dataType    :'json',
                url         : base_url + '/genes/is_valid/',
                data        : JSON.stringify({'genes-list' : list}),
                beforeSend  : function(xhr){xhr.setRequestHeader("X-CSRFToken", csrftoken);},
                success : function (data) {
                    if(!data.error) {
                        cb(data.results);
                    }
                },
                error: function () {
                    console.log('Failed to add gene_lists to workbook');
                }
            });
        }
    }

    function checkUploadedGeneListAgainstGeneIdentifier(gene_list){
        // Delete any repetitive genes
        var genes = truncateRepeatGenes(gene_list);
        var invalid_genes = [];
        var valid_genes = [];

        genes.forEach(function(gene){
            if(gene_list.indexOf(gene.toUpperCase()) < 0){
                invalid_genes.push(gene);
            }else{
                valid_genes.push(gene);
            }
        })
        if(invalid_genes.length > 0){
            //If some genes cannot be identified
            //hide default upload panel and show error panel
            $('#error-panel').addClass('in');
            $('#upload-panel').addClass('collapse');
            $('#error-genes').text(invalid_genes.join(' '));
        } else{
            //genes_upload(valid_genes);
        }
        return {invalid: invalid_genes, valid: valid_genes}
    }

    function truncateRepeatGenes(genes){
        var genes_count_object = {};
        genes.forEach(function(gene){
            if(genes_count_object[gene]){
                genes_count_object[gene] += 1;
            }else{
                genes_count_object[gene] = 1;
            }
        });
        return Object.keys(genes_count_object);
    }
});<commit_msg>-> Duplicates will now be unflagged upon removal of extras in an orderly fashion -> Duplication and 'unrecognized gene symbol' will no longer override one another<commit_after>require.config({
    baseUrl: '/static/js/',
    paths: {
        jquery: 'libs/jquery-1.11.1.min',
        bootstrap: 'libs/bootstrap.min',
        jqueryui: 'libs/jquery-ui.min',
        session_security: 'session_security',
        underscore: 'libs/underscore-min',
        assetscore: 'libs/assets.core',
        assetsresponsive: 'libs/assets.responsive',
        base: 'base',
        bloodhound: 'libs/bloodhound',
        typeahead : 'libs/typeahead',
        tokenfield: 'libs/bootstrap-tokenfield.min'
    },
    shim: {
        'bootstrap': ['jquery'],
        'jqueryui': ['jquery'],
        'session_security': ['jquery'],
        'assetscore': ['jquery', 'bootstrap', 'jqueryui'],
        'assetsresponsive': ['jquery', 'bootstrap', 'jqueryui'],
        'underscore': {exports: '_'},
        'tokenfield': ['jquery', 'jqueryui'],
        'typeahead':{
            deps: ['jquery'],
            init: function ($) {
                return require.s.contexts._.registry['typeahead.js'].factory( $ );
            }
        },
        'bloodhound': {
           deps: ['jquery'],
           exports: 'Bloodhound'
        }
    }
});

require([
    'jquery',
    'jqueryui',
    'bootstrap',
    'session_security',
    'bloodhound',
    'typeahead',
    'underscore',
    'assetscore',
    'assetsresponsive',
    'base',
    'tokenfield'
], function($, jqueryui, bootstrap, session_security, Bloodhound, typeahead, _) {
    'use strict';

    var geneListField = $('#paste-in-genes');
    var geneFavs = (gene_fav) ? gene_fav.genes : [];

    if(typeof(uploaded_genes)!== 'undefined' && uploaded_genes.length > 0 ){
        geneFavs = geneFavs.concat(uploaded_genes);
    }

    //create bloodhound typeahead engine for gene suggestions
    var gene_suggestions = new Bloodhound({
        datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
        queryTokenizer: Bloodhound.tokenizers.whitespace,
        prefetch : base_url + '/genes/suggest/a.json',
        remote: {
            url: base_url + '/genes/suggest/%QUERY.json',
            wildcard: '%QUERY'
        }
    });
    gene_suggestions.initialize();
    function createTokenizer() {
        // be aware bootstrap tokenfield requires 'value' as the datem attribute field : https://github.com/sliptree/bootstrap-tokenfield/issues/189
        geneListField.tokenfield({
            typeahead : [
                null, {
                    source: gene_suggestions.ttAdapter(),
                    display: 'value'
                }
            ],
            delimiter : " ",
            minLength: 2,
            tokens: geneFavs
        }).on('tokenfield:createtoken',function(event){
            //  Check whether the user enter a repetitive token
            //  If it is a repetitive token, show a message instead
            var existingGenes = geneListField.tokenfield('getTokens');
            $.each(existingGenes, function (index, gene) {
                if (gene.value.toUpperCase() === event.attrs.value.toUpperCase()) {
                    event.attrs.isRepeatEntry = true;
                    $('.helper-text__repeat').show();
                }
            });
        }).on('tokenfield:createdtoken', function (event) {
            event.attrs.isRepeatEntry && $(event.relatedTarget).addClass('invalid repeat repeat-of-'+event.attrs.value);

            // Check whether user entered a valid gene name
            validate_genes([event.attrs.value], function validCallback(result){
                if(!result[event.attrs.value]){
                    $(event.relatedTarget).addClass('invalid error');
                    $('.helper-text__invalid').show();
                }
                if ($('div.token.invalid.error').length < 1) {
                    $('.helper-text__invalid').hide();
                }
                if ($('div.token.invalid.repeat').length < 1) {
                    $('.helper-text__repeat').hide();
                }
            })
        }).on('tokenfield:removedtoken', function (event) {
            // Update duplicate flagging
            var theseRepeats = [];
            if($('div.repeat-of-'+event.attrs.value).length > 0) {
                var firstRepeat = $('div.repeat-of-'+event.attrs.value).first();
                firstRepeat.removeClass('repeat repeat-of-'+event.attrs.value);
                if(!firstRepeat.hasClass('error')){
                    firstRepeat.removeClass('invalid');
                }
            }

            if ($('div.token.invalid.error').length < 1) {
                $('.helper-text__invalid').hide();
            }
            if ($('div.token.invalid.repeat').length < 1) {
                $('.helper-text__repeat').hide();
            }
        }).on('tokenfield:edittoken',function(e){
            e.preventDefault();
            return false;
        });
    }
    createTokenizer();

    // Clear all entered genes list on click
    $('#clearAll').on('click', function (event) {
        if($('.tokenfield').hasClass('focus')){
            // the tokenfield takes enter key and click event, and it will trigger the clear all method,
            // this code checks whether tokenfield is on focus, if yes, it preventDefault and do nothing
            event.preventDefault();
        }else{
            geneFavs = [];
            geneListField.tokenfield('setTokens', []);
            geneListField.tokenfield('createToken' , 'this is an arbitrary token to get bootstrap to clear the existing tokens');
            geneListField.tokenfield('setTokens', []);
        }
        return false;
    });

    // Genes upload page, file upload button onclick
    // Check if the browser support formdata for file upload
    var xhr2 = !! ( window.FormData && ("upload" in ($.ajaxSettings.xhr())  ));
    var uploaded_list;
    if(!xhr2 || !window.File || !window.FileReader || !window.FileList || !window.Blob){
        var errorMsg = 'Your browser doesn\'t support file upload. You can paste your gene list from the option below.';
        $('#file-upload-btn').attr('disabled', true).parent()
                             .append('
' + errorMsg + '
');
    }else{
        // If file upload is supported
        var fileUploadField = $('#file-upload-field');
        var validFileTypes = ['txt', 'csv'];

        $('#file-upload-btn').click(function(event){
            event.preventDefault()
            fileUploadField.click();
        });
        fileUploadField.on('change', function(event){
            var file = this.files[0];
            var ext = file.name.split(".").pop().toLowerCase();

            // check file format against valid file types
            if(validFileTypes.indexOf(ext) < 0){
                // If file type is not correct
                alert('Please upload a .txt or .csv file');
                return false;
            } else{
                $('#selected-file-name').text(file.name);
                $('#uploading').addClass('in');
                $('#file-upload-btn').hide();
            }

            if(event.target.files != undefined){
                var fr = new FileReader();
                var uploaded_gene_list;
                fr.onload = function(event){
                    uploaded_gene_list = fr.result.split(/[ \(,\)]+/).filter(Boolean);

                    // Send the uploaded gene's list to the backend
                    uploaded_list = checkUploadedGeneListAgainstGeneIdentifier(uploaded_gene_list);
                    for(var i in uploaded_list.valid){
                         geneListField.tokenfield('createToken', uploaded_list.valid[i]);
                    }
                    for(var i in uploaded_list.invalid){
                         geneListField.tokenfield('createToken', uploaded_list.invalid[i]);
                    }

                    $('#uploading').removeClass('in');
                    $('#file-upload-btn').show();
                }
                fr.readAsText(event.target.files.item(0));
            }
        })
    }

    //$('#paste-upload').on('click', function(){
    //    var pasted_genes = $($(this).data('target')).val().split(/[ ,]+/).filter(Boolean);
    //
    //    uploaded_list = checkUploadedGeneListAgainstGeneIdentifier(pasted_genes);
    //    console.log(pasted_genes);
    //})

    //$('#clearAll').on('click', function(){
    //    $('#paste-in-genes').val('');
    //})
    //$('#upload-without-error-genes').on('click', function(){
    //    // upload valid gene list
    //    // uploaded_list.valid
    //    console.log(uploaded_list.valid);
    //})
    //$('#upload-with-error-genes').on('click', function(){
    //    var newlist = $($(this).data('target')).val().split(/[ ,]+/).filter(Boolean);
    //    newlist = uploaded_list.valid.concat(newlist);
    //
    //    // Upload newlist
    //    console.log(uploaded_list.valid);
    //})

    $('form.create-gene-list').on('submit', function() {
        $(this).find('input[type="submit"]').attr('disabled', 'disabled');
    });

    /*
        Used for getting the CORS token for submitting data
     */
    function get_cookie(name) {
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }

    function validate_genes(list, cb){
        if(list.length > 0){
            var csrftoken = get_cookie('csrftoken');
            $.ajax({
                type        : 'POST',
                dataType    :'json',
                url         : base_url + '/genes/is_valid/',
                data        : JSON.stringify({'genes-list' : list}),
                beforeSend  : function(xhr){xhr.setRequestHeader("X-CSRFToken", csrftoken);},
                success : function (data) {
                    if(!data.error) {
                        cb(data.results);
                    }
                },
                error: function () {
                    console.log('Failed to add gene_lists to workbook');
                }
            });
        }
    }

    function checkUploadedGeneListAgainstGeneIdentifier(gene_list){
        // Delete any repetitive genes
        var genes = truncateRepeatGenes(gene_list);
        var invalid_genes = [];
        var valid_genes = [];

        genes.forEach(function(gene){
            if(gene_list.indexOf(gene.toUpperCase()) < 0){
                invalid_genes.push(gene);
            }else{
                valid_genes.push(gene);
            }
        })
        if(invalid_genes.length > 0){
            //If some genes cannot be identified
            //hide default upload panel and show error panel
            $('#error-panel').addClass('in');
            $('#upload-panel').addClass('collapse');
            $('#error-genes').text(invalid_genes.join(' '));
        } else{
            //genes_upload(valid_genes);
        }
        return {invalid: invalid_genes, valid: valid_genes}
    }

    function truncateRepeatGenes(genes){
        var genes_count_object = {};
        genes.forEach(function(gene){
            if(genes_count_object[gene]){
                genes_count_object[gene] += 1;
            }else{
                genes_count_object[gene] = 1;
            }
        });
        return Object.keys(genes_count_object);
    }
});<|endoftext|>


<commit_before>"use strict";

require('./').def = function (name, value) {
    if (name in this) {
        throw new Error("The method 'foo' already exists on this prototype");
    }

    this[name] = value;

    return this;
};
<commit_msg>Bugfix, no hardcoded 'foo'<commit_after>"use strict";

require('./').def = function (name, value) {
    if (name in this) {
        throw new Error("The method '" + name + "' already exists on this prototype");
    }

    this[name] = value;

    return this;
};
<|endoftext|>