<commit_before>/*jslint indent: 2, plusplus: true*/
"use strict";

var
  clock = require('./clock.js'),
  proxyConfig = require('./config.js').proxy,
  Syncer = require('./syncer.js').Syncer,
  randomRange = require('./statistics.js').randomRange,
  Streamer = require('./streamer.js').Streamer,
  AnswerChecker = require('./answer_checker.js'),
  PlaylistIterator = require('./shuffle_playlist_iterator.js'),
  mediaAuthenticator = new (require('./auth.js').MediaAuthenticator)();

exports.ChatRoom = function (desc, chat, proxy) {

  var
    that = this,
    clients = {},
    localPersonData = {},
    numberOfClients = 0,
    playlistIterator,
    answerChecker,
    streamer,

    // "dead", "playing", "suspense"
    // if state is playing, when did the song started?
    // if state is suspense, when will the next song start?
    roomState = {
      state : "dead",
      songStart : null,
      lastSong : null,
      lastScore : null
    };

  function packRoomState() {
    var id, sol = {
      desc : {
        name : desc.name,
        desc : desc.desc,
        playlist : {
          length : desc.playlist.length
        }
      },
      users : {},
      state : roomState
    };
    for (id in clients) {
      if (clients.hasOwnProperty(id)) {
        sol.users[id] = clients[id].publicInfo();
      }
    }
    return sol;
  }

  function playNext() {
    if (!desc.playlist.length) {
      return;
    }
    streamer.play(playlistIterator.nextItem(), function (startTime, err) {
      if (err) {
        console.log("tried to play next: " + err);
        info("Tried to play next with error (retry in 5s): " + err);
        setTimeout(playNext, 5000);
      } else {
        roomState.state = "after";
        roomState.lastSong = playlistIterator.lastItem();
        roomState.songStart = null;

        setTimeout(function() {
          roomState.state = "suspense";
          roomState.songStart = startTime;
          that.broadcast('next_song_announce', roomState);
        }, Math.max(0, startTime - clock.clock() - 6000));

        setTimeout(function() {
          roomState.state = "playing";
        }, Math.max(0, startTime - clock.clock()));
      }
    });
  }

  function info(text, to) {
    if (to) {
      to.send('say', {
        when : clock.clock(),
        to   : to.id(),
        from : null,
        what : text
      });
    } else {
      that.broadcast('say', {
        when : clock.clock(),
        to   : null,
        from : null,
        what : text
      });
    }
  }

  // if local record for this person (pid) is missing,
  // initialize to {}.
  // assign that record to a client.
  function initLocalData(client) {
    if (!localPersonData.hasOwnProperty(client.pid())) {
      localPersonData[client.pid()] = {
        score : 0,
        num : 0 // numer of your accounts connected
      };
    }
    client.desc('local', localPersonData[client.pid()]);
  }

  function onSay(data, client) {
    // debug
    if (!clients.hasOwnProperty(data.from)) {
      console.log("internal: from not in this room");
      throw "internal: from not in this room";
    }
    if (data.from !== client.id()) {
      throw "from is not you";
    }
    if (data.to !== null && !clients.hasOwnProperty(data.to)) {
      throw "to specified, but not in this room";
    }
    that.broadcast('say', data);
    // only if state is playing check for correct answer.
    if (roomState.state !== "playing") {
      return;
    }
    if (answerChecker.checkAnswer(playlistIterator.currentItem(), data.what)) {
      client.local('score', client.local('score') + 1);
      roomState.lastScore = client.id();
      that.broadcast('correct_answer', {
        who: client.id(),
        answer: playlistIterator.currentItem(),
        when : data.when
      });
      playNext();
    }
  }

  function onNewRoom(data, client) {
    if (!chat.roomNameExists(data)) {
      return client.error("no such room " + data + ".", 1);
    }
    chat.move(client, chat.whereIs(client), chat.getRoomByName(data));
  }

  function onNext(data, client) {
    if (roomState.state !== "playing") {
      return;
    }
    that.broadcast('called_next', {
      who: client.id(),
      answer: playlistIterator.currentItem(),
      when : data.when
    });
    playNext();
  }

  function onToken(data, client) {
    client.send(
      'token',
      mediaAuthenticator.issueToken(client.desc('email'))
    );
  }

  function onResetScore(data, client) {
    client.local('score', 0);
    that.broadcast('called_reset', {
      who: client.id(),
      when: data.when
    });
  }

  function chunkHandler(chunkInfo) {
    proxy.proxify(chunkInfo.url, function (url, url2) {
      var id;
      chunkInfo.url = url;
      chunkInfo.backupUrl = url2;
      for (id in clients) {
        if (clients.hasOwnProperty(id)) {
          setTimeout(
            clients[id].send.bind(this, "chunk", chunkInfo),
            1000 * randomRange(
              proxyConfig.throttleStreamOff,
              proxyConfig.throttleStreamAmp
            )
          );
        }
      }
    });
  }

  function onHonor(data, client) {
    var target;
    if (!clients.hasOwnProperty(data.to)) {
      return info("Target acc is not in da klub.");
    }
    target = clients[data.id];
    if (roomState.state !== "after") {
      return info("Can't honor in this moment.", client);
    }
    if (target === client) {
      return info("You are honored.. gee, well done!", client);
    }
    if (client.id() !== roomState.lastScore) {
      return info("You didn't made the last score.", client);
    }
    client.local('score', client.local('score') - 1);
    target.local('score', target.local('score') + 1);
    roomState.lastScore = target.id();
    that.broadcast('honored', {from: client.id(), to: target.id()});
  }

  function songEndedHandler() {
    that.broadcast('song_ended', {
      answer : playlistIterator.currentItem(),
      when : clock.clock()
    });
    playNext();
  }

  // used as a wrapper to Syncer class.
  function SyncSocketWrap(client) {
    var that = this;
    this.onmessage = null;
    this.send = function (msg, done) {
      client.send("sync", msg, done);
    }
    client.onMessage("sync", function (msg) {
      if (that.onmessage) {
        that.onmessage({data:msg});
      }
    });
  }

  function onStartSync(data, client) {
    Syncer(
      new SyncSocketWrap(client),
      function() {}
    );
  }

  this.desc = desc;

  this.broadcast = function (type, msg, except) {
    var id;
    for (id in clients) {
      if (clients.hasOwnProperty(id)) {
        if (!(except && except.id() === id)) {
          clients[id].send(type, msg);
        }
      }
    }
  };

  // this is triggered from ChatClient::local
  // function will copy local data to all other clients
  // with same pid and to room's localPersonData
  this.localDataChanged = function (client) {
    var pid = client.pid(), id;
    localPersonData[pid] = client.desc('local');
    for (id in clients) {
      if (clients.hasOwnProperty(id)) {
        if (clients[id].pid() === pid) {
          clients[id].desc('local', localPersonData[pid]);
        }
      }
    }
  };

  // add a client to a list of clients and
  // notify all clients about the adding.
  this.enter = function (client) {
    numberOfClients++;
    if (numberOfClients === 1) {
      playNext();
    }
    clients[client.id()] = client;
    client.setRoom(that);
    initLocalData(client);
    client.local('num', client.local('num') + 1);
    if (client.local('num') > 3) {
      return client.error("too many accounts in room");
    }

    this.broadcast('new_client', client.publicInfo(), client);
    client.send('room_state', packRoomState());
    client.onMessage('say', onSay);
    client.onMessage('new_room', onNewRoom);
    client.onMessage('next', onNext);
    client.onMessage('token', onToken);
    client.onMessage('reset_score', onResetScore);
    client.onMessage('honor', onHonor);
    client.onMessage('sync_start', onStartSync);
  };

  // pop a client from a list of clients and
  // notify all other clients about the popping :)
  this.leave = function (client, reason) {
    numberOfClients--;
    client.local('num', client.local('num') - 1);
    delete clients[client.id()];
    if (!numberOfClients) {
      roomState.state = "dead";
      roomState.songStart = null;
      roomState.lastSong = null;
      streamer.stop();
    }
    this.broadcast('old_client', [client.id(), reason]);
  };

  (function () {
    playlistIterator = new PlaylistIterator(desc.playlist);
    answerChecker = new AnswerChecker({});
    streamer = new Streamer(chat.media, chunkHandler, songEndedHandler);
  }());
};
<commit_msg>honor hotfix<commit_after>/*jslint indent: 2, plusplus: true*/
"use strict";

var
  clock = require('./clock.js'),
  proxyConfig = require('./config.js').proxy,
  Syncer = require('./syncer.js').Syncer,
  randomRange = require('./statistics.js').randomRange,
  Streamer = require('./streamer.js').Streamer,
  AnswerChecker = require('./answer_checker.js'),
  PlaylistIterator = require('./shuffle_playlist_iterator.js'),
  mediaAuthenticator = new (require('./auth.js').MediaAuthenticator)();

exports.ChatRoom = function (desc, chat, proxy) {

  var
    that = this,
    clients = {},
    localPersonData = {},
    numberOfClients = 0,
    playlistIterator,
    answerChecker,
    streamer,

    // "dead", "playing", "suspense"
    // if state is playing, when did the song started?
    // if state is suspense, when will the next song start?
    roomState = {
      state : "dead",
      songStart : null,
      lastSong : null,
      lastScore : null
    };

  function packRoomState() {
    var id, sol = {
      desc : {
        name : desc.name,
        desc : desc.desc,
        playlist : {
          length : desc.playlist.length
        }
      },
      users : {},
      state : roomState
    };
    for (id in clients) {
      if (clients.hasOwnProperty(id)) {
        sol.users[id] = clients[id].publicInfo();
      }
    }
    return sol;
  }

  function playNext() {
    if (!desc.playlist.length) {
      return;
    }
    streamer.play(playlistIterator.nextItem(), function (startTime, err) {
      if (err) {
        console.log("tried to play next: " + err);
        info("Tried to play next with error (retry in 5s): " + err);
        setTimeout(playNext, 5000);
      } else {
        roomState.state = "after";
        roomState.lastSong = playlistIterator.lastItem();
        roomState.songStart = null;

        setTimeout(function() {
          roomState.state = "suspense";
          roomState.songStart = startTime;
          that.broadcast('next_song_announce', roomState);
        }, Math.max(0, startTime - clock.clock() - 6000));

        setTimeout(function() {
          roomState.state = "playing";
        }, Math.max(0, startTime - clock.clock()));
      }
    });
  }

  function info(text, to) {
    if (to) {
      to.send('say', {
        when : clock.clock(),
        to   : to.id(),
        from : null,
        what : text
      });
    } else {
      that.broadcast('say', {
        when : clock.clock(),
        to   : null,
        from : null,
        what : text
      });
    }
  }

  // if local record for this person (pid) is missing,
  // initialize to {}.
  // assign that record to a client.
  function initLocalData(client) {
    if (!localPersonData.hasOwnProperty(client.pid())) {
      localPersonData[client.pid()] = {
        score : 0,
        num : 0 // numer of your accounts connected
      };
    }
    client.desc('local', localPersonData[client.pid()]);
  }

  function onSay(data, client) {
    // debug
    if (!clients.hasOwnProperty(data.from)) {
      console.log("internal: from not in this room");
      throw "internal: from not in this room";
    }
    if (data.from !== client.id()) {
      throw "from is not you";
    }
    if (data.to !== null && !clients.hasOwnProperty(data.to)) {
      throw "to specified, but not in this room";
    }
    that.broadcast('say', data);
    // only if state is playing check for correct answer.
    if (roomState.state !== "playing") {
      return;
    }
    if (answerChecker.checkAnswer(playlistIterator.currentItem(), data.what)) {
      client.local('score', client.local('score') + 1);
      roomState.lastScore = client.id();
      that.broadcast('correct_answer', {
        who: client.id(),
        answer: playlistIterator.currentItem(),
        when : data.when
      });
      playNext();
    }
  }

  function onNewRoom(data, client) {
    if (!chat.roomNameExists(data)) {
      return client.error("no such room " + data + ".", 1);
    }
    chat.move(client, chat.whereIs(client), chat.getRoomByName(data));
  }

  function onNext(data, client) {
    if (roomState.state !== "playing") {
      return;
    }
    that.broadcast('called_next', {
      who: client.id(),
      answer: playlistIterator.currentItem(),
      when : data.when
    });
    playNext();
  }

  function onToken(data, client) {
    client.send(
      'token',
      mediaAuthenticator.issueToken(client.desc('email'))
    );
  }

  function onResetScore(data, client) {
    client.local('score', 0);
    that.broadcast('called_reset', {
      who: client.id(),
      when: data.when
    });
  }

  function chunkHandler(chunkInfo) {
    proxy.proxify(chunkInfo.url, function (url, url2) {
      var id;
      chunkInfo.url = url;
      chunkInfo.backupUrl = url2;
      for (id in clients) {
        if (clients.hasOwnProperty(id)) {
          setTimeout(
            clients[id].send.bind(this, "chunk", chunkInfo),
            1000 * randomRange(
              proxyConfig.throttleStreamOff,
              proxyConfig.throttleStreamAmp
            )
          );
        }
      }
    });
  }

  function onHonor(data, client) {
    var target;
    if (!clients.hasOwnProperty(data.to)) {
      return info("Target acc is not in da klub.", client);
    }
    target = clients[data.to];
    if (roomState.state !== "after") {
      return info("Can't honor in this moment.", client);
    }
    if (target === client) {
      return info("You are honored.. gee, well done!", client);
    }
    if (client.id() !== roomState.lastScore) {
      return info("You didn't made the last score.", client);
    }
    client.local('score', client.local('score') - 1);
    target.local('score', target.local('score') + 1);
    roomState.lastScore = target.id();
    that.broadcast('honored', {from: client.id(), to: target.id()});
  }

  function songEndedHandler() {
    that.broadcast('song_ended', {
      answer : playlistIterator.currentItem(),
      when : clock.clock()
    });
    playNext();
  }

  // used as a wrapper to Syncer class.
  function SyncSocketWrap(client) {
    var that = this;
    this.onmessage = null;
    this.send = function (msg, done) {
      client.send("sync", msg, done);
    }
    client.onMessage("sync", function (msg) {
      if (that.onmessage) {
        that.onmessage({data:msg});
      }
    });
  }

  function onStartSync(data, client) {
    Syncer(
      new SyncSocketWrap(client),
      function() {}
    );
  }

  this.desc = desc;

  this.broadcast = function (type, msg, except) {
    var id;
    for (id in clients) {
      if (clients.hasOwnProperty(id)) {
        if (!(except && except.id() === id)) {
          clients[id].send(type, msg);
        }
      }
    }
  };

  // this is triggered from ChatClient::local
  // function will copy local data to all other clients
  // with same pid and to room's localPersonData
  this.localDataChanged = function (client) {
    var pid = client.pid(), id;
    localPersonData[pid] = client.desc('local');
    for (id in clients) {
      if (clients.hasOwnProperty(id)) {
        if (clients[id].pid() === pid) {
          clients[id].desc('local', localPersonData[pid]);
        }
      }
    }
  };

  // add a client to a list of clients and
  // notify all clients about the adding.
  this.enter = function (client) {
    numberOfClients++;
    if (numberOfClients === 1) {
      playNext();
    }
    clients[client.id()] = client;
    client.setRoom(that);
    initLocalData(client);
    client.local('num', client.local('num') + 1);
    if (client.local('num') > 3) {
      return client.error("too many accounts in room");
    }

    this.broadcast('new_client', client.publicInfo(), client);
    client.send('room_state', packRoomState());
    client.onMessage('say', onSay);
    client.onMessage('new_room', onNewRoom);
    client.onMessage('next', onNext);
    client.onMessage('token', onToken);
    client.onMessage('reset_score', onResetScore);
    client.onMessage('honor', onHonor);
    client.onMessage('sync_start', onStartSync);
  };

  // pop a client from a list of clients and
  // notify all other clients about the popping :)
  this.leave = function (client, reason) {
    numberOfClients--;
    client.local('num', client.local('num') - 1);
    delete clients[client.id()];
    if (!numberOfClients) {
      roomState.state = "dead";
      roomState.songStart = null;
      roomState.lastSong = null;
      streamer.stop();
    }
    this.broadcast('old_client', [client.id(), reason]);
  };

  (function () {
    playlistIterator = new PlaylistIterator(desc.playlist);
    answerChecker = new AnswerChecker({});
    streamer = new Streamer(chat.media, chunkHandler, songEndedHandler);
  }());
};
<|endoftext|>


<commit_before>(function() {
	var Semanticolor;
	var SemanticolorView = require('./semanticolor-view');
	var CompositeDisposable = require('atom').CompositeDisposable;
	var SemanticolorGrammarFactory = require('./semanticolor-grammar');

	var _ = require('lodash');
	var Sugar = require('sugar');

	var fs = require('fs');
	var path = require('path');

	var lessFile = path.join(__dirname, '..', 'styles', 'semanticolor.less');
	var grammarListFile = path.join(__dirname, 'grammars.txt');
	var ignoredFileTypes = ['md', 'diff', 'json', 'cson'];

	var grammars = {};
	var options = ['contrast', 'mute', 'theme', 'colorize'];
	var config = {
		colorOptions: {
			description: 'Options that affect how colors are generated.',
			type: 'object',
			order: 0,
			properties: {
				hues: {
					type: 'integer',
					title: 'hues',
					description: 'Fewer colors may make them easier to distinguish, but they will be repeated more often.',
					minimum: 8,
					default: 700,
					maximum: 700,
					order: 1
				},
				saturation: {
					type: 'number',
					title: 'saturation',
					description: 'Color stauration (0 to 100%).',
					minimum: 0.1,
					default: 90,
					maximum: 100,
					order: 2
				},
				luminosity: {
					type: 'number',
					title: 'luminosity',
					description: 'Color luminosity (0 to 100%).',
					minimum: 0.1,
					default: 50,
					maximum: 100,
					order: 3
				},
				fade: {
					type: 'number',
					title: 'fade',
					description: 'Color fade (0 to 100%).',
					minimum: 0.1,
					default: 40,
					maximum: 100,
					order: 4
				}
			}
		}
	};

	var editorOptions = {
		type: 'object',
		description: 'Options that affect how colors are applied.',
		collapsed: true,
		properties: {
			enabled: {
				type: 'boolean',
				title: 'Enabled',
				default: 'true',
				order: 0
			},
			comment: {
				type: 'string',
				title: 'Comments',
				default: 'contrast',
				enum: options,
				order: 1
			},
			primitive: {
				type: 'string',
				title: 'Primitives',
				default: 'mute',
				enum: options,
				order: 2
			},
			storage_modifier: {
				type: 'string',
				title: 'Storage modifiers',
				default: 'mute',
				enum: options,
				order: 3
			},
			storage_type: {
				type: 'string',
				title: 'Storage types',
				default: 'mute',
				enum: options,
				order: 4
			},
			support_type: {
				type: 'string',
				title: 'Support types',
				default: 'colorize',
				enum: options,
				order: 5
			},
			language: {
				type: 'string',
				title: 'Language elements',
				default: 'mute',
				enum: options,
				order: 6
			},
			keyword: {
				type: 'string',
				title: 'Keywords',
				default: 'mute',
				enum: options,
				order: 7
			},
			operator: {
				type: 'string',
				title: 'Operators',
				default: 'mute',
				enum: options,
				order: 8
			},
			punctuation: {
				type: 'string',
				title: 'Punctuation',
				default: 'mute',
				enum: options,
				order: 9
			},
			markup: {
				type: 'string',
				title: 'Markup',
				default: 'theme',
				enum: options,
				order: 10
			},
			string: {
				type: 'string',
				title: 'String constants',
				default: 'theme',
				enum: options,
				order: 11
			},
			support_constant: {
				type: 'string',
				title: 'Support constants',
				default: 'theme',
				enum: options,
				order: 12
			},
			constant_numeric: {
				type: 'string',
				title: 'Numeric constants',
				default: 'theme',
				enum: options,
				order: 13
			}
		}
	};

	var grammarList = [];
	try {
		grammarList = Sugar.Array.compact(fs.readFileSync(grammarListFile, { encoding: 'utf-8' }).split(/\r\n|\r|\n/), true);
	} catch (e) {
		grammarList = [];
	}

	for (var i = 0; i < grammarList.length; i++) {
		addGrammarToConfig(grammarList[i]);
	}

	function getGrammarName(grammar) {
		var result = grammar.name + ' (' + grammar.packageName + ')';
		return result;
	}

	function getGrammarParamNameFromGrammarName(grammarName) {
		return Sugar.String.camelize(Sugar.String.parameterize(grammarName), false);
	}

	function addGrammarToConfig(grammarName) {
		var options = Sugar.Object.clone(editorOptions);
		options.title = grammarName;
		config[getGrammarParamNameFromGrammarName(grammarName)] = options;
	}

	function writeGrammarListFile() {
		var fileContents = '';
		for (var i = 0; i < grammarList.length; i++) {
			fileContents += grammarList[i] + '\n';
		}
		fs.writeFileSync(grammarListFile, fileContents, { encoding: 'utf-8' });
	}

	module.exports = Semanticolor = {
		config: config,
		semanticolorView: SemanticolorView,
		modalPanel: SemanticolorView,
		disposables: new CompositeDisposable(),
		updateLessStylesheet: function(skipReload) {
			var cfg = atom.config.get('semanticolor').colorOptions;
			var less = makeLess(cfg.hues, cfg.saturation, cfg.luminosity, cfg.fade);
			var written = false;
			try {
				var currentLess = fs.readFileSync(lessFile, { encoding: 'utf-8' });
				if (currentLess !== less) {
					fs.writeFileSync(lessFile, less, { encoding: 'utf-8' });
					written = true;
				}
				if (written) {
					atom.notifications.addSuccess('Rewrote new colors...', {
						detail: 'Reloading with stylesheet of ' + cfg.hues + ' possible colors.'
					});
					if (!skipReload) {
						this.confirmReload();
					}
				}
			} catch (e) {
				atom.notifications.addError('No initial colors configured...', {
					detail: 'Updating stylesheet with default of ' + this.config.colorOptions.hues.default+
						' possible colors.'
				});
				try {
					less = makeLess(this.config.colorOptions.hues.default, this.config.colorOptions.saturation.default,
						this.config.colorOptions.luminosity.default, this.config.colorOptions.fade.default);
					written = fs.writeFileSync(lessFile, less, { encoding: 'utf-8' });
				} catch (err) {
					console.log(err);
					atom.notifications.addError(e.code + ' : ' + e.message, {
						detail: 'Something failed. Open an issue with me!'
					});
				}
			}

			return written;

			function makeLess(hues, saturation, luminosity, fade) {
				return '@hues: ' + hues + ';\n' +
					'@saturation: ' + saturation + '%;\n' +
					'@luminosity: ' + luminosity + '%;\n' +
					'@fade: ' + fade + '%;';
			}
		},
		activate: function(state) {
			this.updateLessStylesheet(true);
			this.deactivated = false;
			this.semanticolorView = new SemanticolorView();
			var cfg = atom.config.get('semanticolor');
			for (var key in cfg) {
				if (!Semanticolor.config[key]) {
					delete cfg[key];
				}
			}
			atom.config.set('semanticolor', cfg);

			var temp = atom.grammars.getGrammars();
			for (var i = 0; i < temp.length; i++) {
				createGrammar(temp[i]);
			}

			atom.grammars.onDidAddGrammar(function(grammar) {
				createGrammar(grammar);
			});

			function createGrammar(grammar) {
				var paramName = tryAddGrammarToConfig(grammar);
				if (paramName && cfg[paramName].enabled) {
					grammars[paramName] = SemanticolorGrammarFactory.create(grammar, cfg[paramName], cfg.colorOptions.hues);
				}
			}

			function tryAddGrammarToConfig(grammar) {
				var grammarName = getGrammarName(grammar);
				if (grammarList.includes(grammarName)) {
					return getGrammarParamNameFromGrammarName(grammarName);
				}
				if (grammar.packageName !== SemanticolorGrammarFactory.packageName &&
					grammar.scopeName.includes('source') && !/\..+\./.test(grammar.scopeName) &&
					Sugar.Array.intersect(grammar.fileTypes, ignoredFileTypes).length == 0) {
					grammarList.push(grammarName);
					writeGrammarListFile();
					console.log('added ' + grammarName, grammar);
					Semanticolor.confirmReload();
				}
			}

			this.disposables.add(atom.workspace.observeTextEditors(enable));

			var editors = atom.workspace.getTextEditors();
			for (var i = 0; i < editors.length; i++) {
				enable(editors[i]);
			}

			this.disposables.add(atom.config.onDidChange('semanticolor', function(change) {
				if (Semanticolor.deactivated) return;
				_.debounce(function() {
					Semanticolor.updateLessStylesheet(change.newValue.colorOptions.hues === change.oldValue.colorOptions.hues);
					if (!Object.equal(change.newValue.editorOptions, change.oldValue.editorOptions)) {
						Semanticolor.confirmReload();
					}
				}, 1000)();
			}));

			function enable(editor) {
				var grammar = editor.getGrammar();
				var paramName = getGrammarParamNameFromGrammarName(getGrammarName(grammar));
				if (grammars[paramName]) {
					editor.setGrammar(grammars[paramName]);
				}
			}
		},
		confirmReload: function() {
			if (this.deactivated) {
				return;
			}
			this.modalPanel = atom.workspace.addModalPanel({
				item: this.semanticolorView.getElement(),
				visible: true
			});
		},
		deactivate: function() {
			this.deactivated = true;
			if (this.modalPanel && typeof this.modalPanel.destroy === 'function') {
				this.modalPanel.destroy();
			}
			var disposable = this.disposables;
			this.disposables = new CompositeDisposable();
			disposable.dispose();
			return this.semanticolorView.destroy();
		},
	};

}).call(this);
<commit_msg>Fix Sugar call<commit_after>(function() {
	var Semanticolor;
	var SemanticolorView = require('./semanticolor-view');
	var CompositeDisposable = require('atom').CompositeDisposable;
	var SemanticolorGrammarFactory = require('./semanticolor-grammar');

	var _ = require('lodash');
	var Sugar = require('sugar');

	var fs = require('fs');
	var path = require('path');

	var lessFile = path.join(__dirname, '..', 'styles', 'semanticolor.less');
	var grammarListFile = path.join(__dirname, 'grammars.txt');
	var ignoredFileTypes = ['md', 'diff', 'json', 'cson'];

	var grammars = {};
	var options = ['contrast', 'mute', 'theme', 'colorize'];
	var config = {
		colorOptions: {
			description: 'Options that affect how colors are generated.',
			type: 'object',
			order: 0,
			properties: {
				hues: {
					type: 'integer',
					title: 'hues',
					description: 'Fewer colors may make them easier to distinguish, but they will be repeated more often.',
					minimum: 8,
					default: 700,
					maximum: 700,
					order: 1
				},
				saturation: {
					type: 'number',
					title: 'saturation',
					description: 'Color stauration (0 to 100%).',
					minimum: 0.1,
					default: 90,
					maximum: 100,
					order: 2
				},
				luminosity: {
					type: 'number',
					title: 'luminosity',
					description: 'Color luminosity (0 to 100%).',
					minimum: 0.1,
					default: 50,
					maximum: 100,
					order: 3
				},
				fade: {
					type: 'number',
					title: 'fade',
					description: 'Color fade (0 to 100%).',
					minimum: 0.1,
					default: 40,
					maximum: 100,
					order: 4
				}
			}
		}
	};

	var editorOptions = {
		type: 'object',
		description: 'Options that affect how colors are applied.',
		collapsed: true,
		properties: {
			enabled: {
				type: 'boolean',
				title: 'Enabled',
				default: 'true',
				order: 0
			},
			comment: {
				type: 'string',
				title: 'Comments',
				default: 'contrast',
				enum: options,
				order: 1
			},
			primitive: {
				type: 'string',
				title: 'Primitives',
				default: 'mute',
				enum: options,
				order: 2
			},
			storage_modifier: {
				type: 'string',
				title: 'Storage modifiers',
				default: 'mute',
				enum: options,
				order: 3
			},
			storage_type: {
				type: 'string',
				title: 'Storage types',
				default: 'mute',
				enum: options,
				order: 4
			},
			support_type: {
				type: 'string',
				title: 'Support types',
				default: 'colorize',
				enum: options,
				order: 5
			},
			language: {
				type: 'string',
				title: 'Language elements',
				default: 'mute',
				enum: options,
				order: 6
			},
			keyword: {
				type: 'string',
				title: 'Keywords',
				default: 'mute',
				enum: options,
				order: 7
			},
			operator: {
				type: 'string',
				title: 'Operators',
				default: 'mute',
				enum: options,
				order: 8
			},
			punctuation: {
				type: 'string',
				title: 'Punctuation',
				default: 'mute',
				enum: options,
				order: 9
			},
			markup: {
				type: 'string',
				title: 'Markup',
				default: 'theme',
				enum: options,
				order: 10
			},
			string: {
				type: 'string',
				title: 'String constants',
				default: 'theme',
				enum: options,
				order: 11
			},
			support_constant: {
				type: 'string',
				title: 'Support constants',
				default: 'theme',
				enum: options,
				order: 12
			},
			constant_numeric: {
				type: 'string',
				title: 'Numeric constants',
				default: 'theme',
				enum: options,
				order: 13
			}
		}
	};

	var grammarList = [];
	try {
		grammarList = Sugar.Array.compact(fs.readFileSync(grammarListFile, { encoding: 'utf-8' }).split(/\r\n|\r|\n/), true);
	} catch (e) {
		grammarList = [];
	}

	for (var i = 0; i < grammarList.length; i++) {
		addGrammarToConfig(grammarList[i]);
	}

	function getGrammarName(grammar) {
		var result = grammar.name + ' (' + grammar.packageName + ')';
		return result;
	}

	function getGrammarParamNameFromGrammarName(grammarName) {
		return Sugar.String.camelize(Sugar.String.parameterize(grammarName), false);
	}

	function addGrammarToConfig(grammarName) {
		var options = Sugar.Object.clone(editorOptions);
		options.title = grammarName;
		config[getGrammarParamNameFromGrammarName(grammarName)] = options;
	}

	function writeGrammarListFile() {
		var fileContents = '';
		for (var i = 0; i < grammarList.length; i++) {
			fileContents += grammarList[i] + '\n';
		}
		fs.writeFileSync(grammarListFile, fileContents, { encoding: 'utf-8' });
	}

	module.exports = Semanticolor = {
		config: config,
		semanticolorView: SemanticolorView,
		modalPanel: SemanticolorView,
		disposables: new CompositeDisposable(),
		updateLessStylesheet: function(skipReload) {
			var cfg = atom.config.get('semanticolor').colorOptions;
			var less = makeLess(cfg.hues, cfg.saturation, cfg.luminosity, cfg.fade);
			var written = false;
			try {
				var currentLess = fs.readFileSync(lessFile, { encoding: 'utf-8' });
				if (currentLess !== less) {
					fs.writeFileSync(lessFile, less, { encoding: 'utf-8' });
					written = true;
				}
				if (written) {
					atom.notifications.addSuccess('Rewrote new colors...', {
						detail: 'Reloading with stylesheet of ' + cfg.hues + ' possible colors.'
					});
					if (!skipReload) {
						this.confirmReload();
					}
				}
			} catch (e) {
				atom.notifications.addError('No initial colors configured...', {
					detail: 'Updating stylesheet with default of ' + this.config.colorOptions.hues.default+
						' possible colors.'
				});
				try {
					less = makeLess(this.config.colorOptions.hues.default, this.config.colorOptions.saturation.default,
						this.config.colorOptions.luminosity.default, this.config.colorOptions.fade.default);
					written = fs.writeFileSync(lessFile, less, { encoding: 'utf-8' });
				} catch (err) {
					console.log(err);
					atom.notifications.addError(e.code + ' : ' + e.message, {
						detail: 'Something failed. Open an issue with me!'
					});
				}
			}

			return written;

			function makeLess(hues, saturation, luminosity, fade) {
				return '@hues: ' + hues + ';\n' +
					'@saturation: ' + saturation + '%;\n' +
					'@luminosity: ' + luminosity + '%;\n' +
					'@fade: ' + fade + '%;';
			}
		},
		activate: function(state) {
			this.updateLessStylesheet(true);
			this.deactivated = false;
			this.semanticolorView = new SemanticolorView();
			var cfg = atom.config.get('semanticolor');
			for (var key in cfg) {
				if (!Semanticolor.config[key]) {
					delete cfg[key];
				}
			}
			atom.config.set('semanticolor', cfg);

			var temp = atom.grammars.getGrammars();
			for (var i = 0; i < temp.length; i++) {
				createGrammar(temp[i]);
			}

			atom.grammars.onDidAddGrammar(function(grammar) {
				createGrammar(grammar);
			});

			function createGrammar(grammar) {
				var paramName = tryAddGrammarToConfig(grammar);
				if (paramName && cfg[paramName].enabled) {
					grammars[paramName] = SemanticolorGrammarFactory.create(grammar, cfg[paramName], cfg.colorOptions.hues);
				}
			}

			function tryAddGrammarToConfig(grammar) {
				var grammarName = getGrammarName(grammar);
				if (grammarList.includes(grammarName)) {
					return getGrammarParamNameFromGrammarName(grammarName);
				}
				if (grammar.packageName !== SemanticolorGrammarFactory.packageName &&
					grammar.scopeName.includes('source') && !/\..+\./.test(grammar.scopeName) &&
					Sugar.Array.intersect(grammar.fileTypes, ignoredFileTypes).length == 0) {
					grammarList.push(grammarName);
					writeGrammarListFile();
					console.log('added ' + grammarName, grammar);
					Semanticolor.confirmReload();
				}
			}

			this.disposables.add(atom.workspace.observeTextEditors(enable));

			var editors = atom.workspace.getTextEditors();
			for (var i = 0; i < editors.length; i++) {
				enable(editors[i]);
			}

			this.disposables.add(atom.config.onDidChange('semanticolor', function(change) {
				if (Semanticolor.deactivated) return;
				_.debounce(function() {
					Semanticolor.updateLessStylesheet(change.newValue.colorOptions.hues === change.oldValue.colorOptions.hues);
					if (!Sugar.Object.equal(change.newValue.editorOptions, change.oldValue.editorOptions)) {
						Semanticolor.confirmReload();
					}
				}, 1000)();
			}));

			function enable(editor) {
				var grammar = editor.getGrammar();
				var paramName = getGrammarParamNameFromGrammarName(getGrammarName(grammar));
				if (grammars[paramName]) {
					editor.setGrammar(grammars[paramName]);
				}
			}
		},
		confirmReload: function() {
			if (this.deactivated) {
				return;
			}
			this.modalPanel = atom.workspace.addModalPanel({
				item: this.semanticolorView.getElement(),
				visible: true
			});
		},
		deactivate: function() {
			this.deactivated = true;
			if (this.modalPanel && typeof this.modalPanel.destroy === 'function') {
				this.modalPanel.destroy();
			}
			var disposable = this.disposables;
			this.disposables = new CompositeDisposable();
			disposable.dispose();
			return this.semanticolorView.destroy();
		},
	};

}).call(this);
<|endoftext|>


<commit_before>var events = require('events')
	, JSONStream = require('JSONStream')
	, util = require('util')
	, Acks = require('./ack');

/**
 * Basic event emitter that interacts with a stream
 */

function SendEmitter () {
	events.EventEmitter.call(this);

	// Handle ready state
	var ready = false;
	this.buffer = [];
	this.__defineSetter__('ready', function (v) {
		var step;
		ready = v;
		if (ready) {
			while (step = this.buffer.shift()) step();
		}
	});
	this.__defineGetter__('ready', function () {
		return ready;
	});

	// Create queue
	this.queue = [];
	this.maxQueueLength = Infinity;

	// Object-mode is disabled by default, for backwards-compatibility
	// Old versions of uhura expect array-formatted messages
	this.objectMode = false;

	// ACK support is disabled by default, depends on object-mode
	this.enableAcks = false;
	this.acks = new Acks();

	// Handle shared session store data
	this.session = {};
	this.on('_set', this._set);
}
util.inherits(SendEmitter, events.EventEmitter);
module.exports = SendEmitter;

/**
 * Set a property of the shared data structure between the server and client
 *
 * @param {mixed} [key] [String key to assign value to, or key/value pair hash]
 * @param {mixed} [val] [Value to assign to designated key, ignored for hashes]
 */

SendEmitter.prototype.set = function (key, val) {
	this.emit('_set', key, val);
	this._send(['_set', key, val]);
};

/**
 * Get value of share data property
 *
 * @param  {string} key [Name of shared property to access]
 */

SendEmitter.prototype.get = function (key) {
	return key ? this.session[key] : this.session;
};

/**
 * Synchronize session state with remote stream
 */

SendEmitter.prototype.syncSession = function () {
	this._send(['_set', this.session]);
};

/**
 * Set local session values
 *
 * @param {mixed} [key] [String key to assign value to, or key/value pair hash]
 * @param {mixed} [val] [Value to assign to designated key, ignored for hashes]
 */

SendEmitter.prototype._set = function (key, val) {
	if (typeof key === 'string')  {
		this.session[key] = val;
		return;
	}

	for (var i in key) {
		this.session[i] = key[i];
	}
};

/**
 * Attach a stream to interact with
 *
 * @param  {Stream} stream [Stream to interact with]
 */

SendEmitter.prototype.attach = function (stream) {
	var ev = this;

	// Create new parser and serializer
	this.serializer = JSONStream.stringify();
	this.parser = JSONStream.parse([true]);
	this.parser.on('data', function (args) {
		// Make server backwards-compatible with old clients
		if (Array.isArray(args)) {
			ev.emit.apply(ev, args);
			return;
		}

		// Send acks on messages that are, themselves, not acks
		if (ev.enableAcks && args.id) {
			ev._send({ args: ['ack::' + args.id] });
		}

		ev.emit.apply(ev, args.args);
	});

	// Attach stream
	this.stream = stream;

	// Start pipes
	this.serializer.pipe(stream).pipe(this.parser);
};

/**
 * Queue event to be sent to the server
 *
 * @param {string} [eventName] [Name of event to emit on the server]
 * @param {mixed}  [...]       [All following arguments are passed to receiver]
 */

SendEmitter.prototype.send = function () {
	var args = Array.prototype.slice.call(arguments);
	var ev = this;

	function withAck (data) {
		// Pop the last argument off of the argument list to act as the callback
		var cb = (typeof args[args.length - 1] === 'function')
			? args.pop()
			: function () {};

		// In the event of a message not being acknowledged,
		// we should requeue the message with modified ACK id.
		function requeue () {
			queue(function () {
				ev._send(data);
			});
		}

		// Create ack listener and add id to data object
		var ack = ev.acks.create(cb);
		data.id = ack.id;

		// When the correct ACK event is received,
		// resolve and detach disconnect event
		ev.once('ack::' + ack.id, function () {
			ev.removeListener('disconnect', requeue);
			ev.acks.resolve(ack.id);
		});

		// Trigger a requeue event if the connection is lost. This ensures
		// that the message is moved to the ACK queue for the new connection.
		ev.once('disconnect', requeue);

		return data;
	}

	// Ready to send data, determine how
	function sendNow () {
		// Attempt to send data in object mode,
		// adding ack functionality, if enabled
		if (ev.objectMode) {
			var data = { args: args };
			ev._send(ev.enableAcks ? withAck(data) : data);
			return;
		}

		ev._send(args);
	}

	// Add something to the callback queue
	// Warn if the queue length gets too large
	function queue (cb) {
		if (ev.buffer.length > ev.maxQueueLength) {
			console.warn('Uhura exceeded maximum queue length, released stale data');
			ev.buffer.shift();
		}
		ev.buffer.push(cb);
	}

	// Determine if we should send immediately, or queue for later
	this.ready ? sendNow() : queue(sendNow);
};

/**
 * Explicitly send data, regardless of ready state
 *
 * @param {string} [eventName] [Name of event to emit on the server]
 * @param {mixed}  [...]       [All following arguments are passed to receiver]
 */

SendEmitter.prototype._send = function (data) {
	this.serializer.write(data);
};
<commit_msg>Move ACK callback locater to ensure it gets ignored by non-ack-supporting servers<commit_after>var events = require('events')
	, JSONStream = require('JSONStream')
	, util = require('util')
	, Acks = require('./ack');

/**
 * Basic event emitter that interacts with a stream
 */

function SendEmitter () {
	events.EventEmitter.call(this);

	// Handle ready state
	var ready = false;
	this.buffer = [];
	this.__defineSetter__('ready', function (v) {
		var step;
		ready = v;
		if (ready) {
			while (step = this.buffer.shift()) step();
		}
	});
	this.__defineGetter__('ready', function () {
		return ready;
	});

	// Create queue
	this.queue = [];
	this.maxQueueLength = Infinity;

	// Object-mode is disabled by default, for backwards-compatibility
	// Old versions of uhura expect array-formatted messages
	this.objectMode = false;

	// ACK support is disabled by default, depends on object-mode
	this.enableAcks = false;
	this.acks = new Acks();

	// Handle shared session store data
	this.session = {};
	this.on('_set', this._set);
}
util.inherits(SendEmitter, events.EventEmitter);
module.exports = SendEmitter;

/**
 * Set a property of the shared data structure between the server and client
 *
 * @param {mixed} [key] [String key to assign value to, or key/value pair hash]
 * @param {mixed} [val] [Value to assign to designated key, ignored for hashes]
 */

SendEmitter.prototype.set = function (key, val) {
	this.emit('_set', key, val);
	this._send(['_set', key, val]);
};

/**
 * Get value of share data property
 *
 * @param  {string} key [Name of shared property to access]
 */

SendEmitter.prototype.get = function (key) {
	return key ? this.session[key] : this.session;
};

/**
 * Synchronize session state with remote stream
 */

SendEmitter.prototype.syncSession = function () {
	this._send(['_set', this.session]);
};

/**
 * Set local session values
 *
 * @param {mixed} [key] [String key to assign value to, or key/value pair hash]
 * @param {mixed} [val] [Value to assign to designated key, ignored for hashes]
 */

SendEmitter.prototype._set = function (key, val) {
	if (typeof key === 'string')  {
		this.session[key] = val;
		return;
	}

	for (var i in key) {
		this.session[i] = key[i];
	}
};

/**
 * Attach a stream to interact with
 *
 * @param  {Stream} stream [Stream to interact with]
 */

SendEmitter.prototype.attach = function (stream) {
	var ev = this;

	// Create new parser and serializer
	this.serializer = JSONStream.stringify();
	this.parser = JSONStream.parse([true]);
	this.parser.on('data', function (args) {
		// Make server backwards-compatible with old clients
		if (Array.isArray(args)) {
			ev.emit.apply(ev, args);
			return;
		}

		// Send acks on messages that are, themselves, not acks
		if (ev.enableAcks && args.id) {
			ev._send({ args: ['ack::' + args.id] });
		}

		ev.emit.apply(ev, args.args);
	});

	// Attach stream
	this.stream = stream;

	// Start pipes
	this.serializer.pipe(stream).pipe(this.parser);
};

/**
 * Queue event to be sent to the server
 *
 * @param {string} [eventName] [Name of event to emit on the server]
 * @param {mixed}  [...]       [All following arguments are passed to receiver]
 */

SendEmitter.prototype.send = function () {
	var args = Array.prototype.slice.call(arguments);
	var ev = this;

	// Pop the last argument off of the argument list to act as the callback
	// NOTE: Do this here to ensure ACK callbacks get ignored by old servers
	var cb = (typeof args[args.length - 1] === 'function')
		? args.pop()
		: function () {};

	function withAck (data) {
		// In the event of a message not being acknowledged,
		// we should requeue the message with modified ACK id.
		function requeue () {
			queue(function () {
				ev._send(data);
			});
		}

		// Create ack listener and add id to data object
		var ack = ev.acks.create(cb);
		data.id = ack.id;

		// When the correct ACK event is received,
		// resolve and detach disconnect event
		ev.once('ack::' + ack.id, function () {
			ev.removeListener('disconnect', requeue);
			ev.acks.resolve(ack.id);
		});

		// Trigger a requeue event if the connection is lost. This ensures
		// that the message is moved to the ACK queue for the new connection.
		ev.once('disconnect', requeue);

		return data;
	}

	// Ready to send data, determine how
	function sendNow () {
		// Attempt to send data in object mode,
		// adding ack functionality, if enabled
		if (ev.objectMode) {
			var data = { args: args };
			if (ev.enableAcks) {
				data = withAck(data);
				ev._send(data);
				return;
			}

			ev._send(data);
			cb();
			return;
		}

		ev._send(args);
		cb();
	}

	// Add something to the callback queue
	// Warn if the queue length gets too large
	function queue (cb) {
		if (ev.buffer.length > ev.maxQueueLength) {
			console.warn('Uhura exceeded maximum queue length, released stale data');
			ev.buffer.shift();
		}
		ev.buffer.push(cb);
	}

	// Determine if we should send immediately, or queue for later
	this.ready ? sendNow() : queue(sendNow);
};

/**
 * Explicitly send data, regardless of ready state
 *
 * @param {string} [eventName] [Name of event to emit on the server]
 * @param {mixed}  [...]       [All following arguments are passed to receiver]
 */

SendEmitter.prototype._send = function (data) {
	this.serializer.write(data);
};
<|endoftext|>


<commit_before>class Mutex
{
  constructor() {
    this.queue = [];
    this.pending = false;
  }

  async exclusive(fn) {
    try {
      var release = await this.acquire();
      return await fn();
    } finally {
      release();
    }
  }

  async acquire() {
    const release = () => {
      this.pending = this.queue.length > 0;
      let next = this.queue.shift();
      next && next();
    };

    if (!this.pending) {
      this.pending = true;
      return release;
    } else {
      await new Promise(resolve => this.queue.push(resolve));
      return release;
    }
  }
}
<commit_msg>Simplify logic.<commit_after>class Mutex
{
  constructor() {
    this.queue = [];
    this.pending = false;
  }

  async exclusive(fn) {
    try {
      var release = await this.acquire();
      return await fn();
    } finally {
      release();
    }
  }

  async acquire() {
    const release = () => {
      this.pending = this.queue.length > 0;
      let next = this.queue.shift();
      next && next();
    };

    if (this.pending) {
      await new Promise(resolve => this.queue.push(resolve));
      return release;
    } else {
      this.pending = true;
      return release;
    }
  }
}
<|endoftext|>


<commit_before>
const _ = require('lodash');
const gulp = require('gulp');
const gls = require('gulp-live-server');
const del = require('del');
const plugins = require('gulp-load-plugins')();

const eslint = require('gulp-eslint');
const babel = require('gulp-babel');
const ejs = require('gulp-ejs');
const browserify = require('browserify');
const babelify = require('babelify');
const source = require('vinyl-source-stream');

const server = gls('./server/server.js');

const {paths} = require('./gulpConfiguration.js');

gulp.task('server', () => {
  server.start();
});

gulp.task('clean', () => {
  return del([paths.dist]);
});

gulp.task('lint', () => {
  return gulp.src([paths.app + '**/*.{js,jsx}'])
    .pipe(eslint())
    .pipe(eslint.format());
});

var sourcePatterns = {
  htmlFlat: '/*.html' ,
  html: '/**/*.html' ,
  images: '/**/*.{gif,jpg,jpeg,png}',
  fonts: '/**/*.{eot,svg,ttf,woff,woff2}',
  sass: '/**/*.scss',
  js: '/**/*.{js,jsx}'
};

var taskList = [
  {
    shortName: 'copy-html',
    sourcePattern: ['htmlFlat'], // string, or array of strings
    create: (sources, config) => {
      return () => {
        return gulp.src(sources)
          .pipe(plugins.plumber())
          .pipe(gulp.dest(paths.dist + config.path))
          .pipe(server.notify.apply(server));
      }
    }
  },{
    shortName: 'ejs',
    sourcePattern: ['html'],
    data: { msg: "Hello from Gulp!" }, // for all ejs templates
    create: (sources, config) => {
      return () => {
        return gulp.src(sources)
          .pipe(ejs(config.data))
          .pipe(gulp.dest(paths.dist + config.path))
          .pipe(server.notify.apply(server));
      }
    }
  },{
    shortName: 'images',
    sourcePattern: ['images'],
    create: (sources, config) => {
      return () => {
        return gulp.src(sources)
          .pipe(plugins.plumber())
          .pipe(gulp.dest(paths.dist + config.path))
          .pipe(server.notify.apply(server));
      }
    }
  },{
    shortName: 'fonts',
    sourcePattern: ['fonts'],
    destination: '/fonts',
    create: (sources, config) => {
      return () => {
        return gulp.src(sources)
          .pipe(plugins.plumber())
          .pipe(gulp.dest(paths.dist + config.path + config.destination))
          .pipe(server.notify.apply(server));
      }
    }
  },{
    shortName: 'sass',
    sourcePattern: ['sass'],
    destination: '/bundle.css',
    create: (sources, config) => {
      return () => {
        return gulp.src(sources)
          .pipe(plugins.plumber())
          .pipe(plugins.concat(config.destination))
          .pipe(plugins.sass({
            outputStyle: 'compressed',
            includePaths: paths.modules
          }))
          .pipe(plugins.autoprefixer())
          .pipe(gulp.dest(paths.dist + config.path))
          .pipe(server.notify.apply(server));
      }
    }
  },{
    shortName: 'js-babel',
    sourcePattern: ['js'],
    destination: '/bundle.js',
    create: (sources, config) => {
      return () => {
        return gulp.src(sources)
          .pipe(plugins.plumber())
          .pipe(plugins.concat(config.destination))
          .pipe(gulp.dest(paths.dist + config.path))
          .pipe(babel({
            presets: ['es2015'],
            babelrc: false
          }))
          .pipe(plugins.uglify())
          .pipe(gulp.dest(paths.dist + config.path))
          .pipe(server.notify.apply(server));
      }
    }
  },{
    shortName: 'js-babel-imports',
    sourcePattern: ['js'], // since there are entry points, this is only what to watch
    create: (sources, config) => {
      return () => {
        // default entry point
        var entryPoints = config.entryPoints || [ 'app.js' ];
        entryPoints = _.map(entryPoints, (entry) => { return paths.app + config.path +'/'+ entry; });
        _.each(entryPoints, (entry) => {
          const destination = entry.split(config.path +'/')[1]; // get the filename from the full path
          return browserify({
            entries: entry, // full path
            extensions: ['.js','.jsx']
          })
          .transform(babelify, {presets: ['es2015']})
          .bundle()
          .pipe(plugins.plumber())
          .pipe(source(destination)) // just the filename
          .pipe(gulp.dest(paths.dist + config.path)) // just the path
          .pipe(server.notify.apply(server));
        });
      }
    }
  },{
    shortName: 'js-react',
    sourcePattern: ['js'], // since there are entry points, this is only what to watch
    create: (sources, config) => {
      return () => {
        // default entry point
        var entryPoints = config.entryPoints || [ 'app.js' ];
        entryPoints = _.map(entryPoints, (entry) => { return paths.app + config.path +'/'+ entry; });
        _.each(entryPoints, (entry) => {
          const destination = entry.split(config.path +'/')[1]; // get the filename from the full path
          return browserify({
            entries: entry, // full path
            extensions: ['.js','.jsx']
          })
          .transform(babelify, {presets: ['es2015', 'react']})
          .bundle()
          .pipe(plugins.plumber())
          .pipe(source(destination)) // just the filename
          .pipe(gulp.dest(paths.dist + config.path)) // just the path
          .pipe(server.notify.apply(server));
        });
      }
    }
  }
];

module.exports = {taskList, sourcePatterns};
<commit_msg>expanding css search pattern<commit_after>
const _ = require('lodash');
const gulp = require('gulp');
const gls = require('gulp-live-server');
const del = require('del');
const plugins = require('gulp-load-plugins')();

const eslint = require('gulp-eslint');
const babel = require('gulp-babel');
const ejs = require('gulp-ejs');
const browserify = require('browserify');
const babelify = require('babelify');
const source = require('vinyl-source-stream');

const server = gls('./server/server.js');

const {paths} = require('./gulpConfiguration.js');

gulp.task('server', () => {
  server.start();
});

gulp.task('clean', () => {
  return del([paths.dist]);
});

gulp.task('lint', () => {
  return gulp.src([paths.app + '**/*.{js,jsx}'])
    .pipe(eslint())
    .pipe(eslint.format());
});

var sourcePatterns = {
  htmlFlat: '/*.html' ,
  html: '/**/*.html' ,
  images: '/**/*.{gif,jpg,jpeg,png}',
  fonts: '/**/*.{eot,svg,ttf,woff,woff2}',
  sass: '/**/*.{scss,sass,css}',
  js: '/**/*.{js,jsx}'
};

var taskList = [
  {
    shortName: 'copy-html',
    sourcePattern: ['htmlFlat'], // string, or array of strings
    create: (sources, config) => {
      return () => {
        return gulp.src(sources)
          .pipe(plugins.plumber())
          .pipe(gulp.dest(paths.dist + config.path))
          .pipe(server.notify.apply(server));
      }
    }
  },{
    shortName: 'ejs',
    sourcePattern: ['html'],
    data: { msg: "Hello from Gulp!" }, // for all ejs templates
    create: (sources, config) => {
      return () => {
        return gulp.src(sources)
          .pipe(ejs(config.data))
          .pipe(gulp.dest(paths.dist + config.path))
          .pipe(server.notify.apply(server));
      }
    }
  },{
    shortName: 'images',
    sourcePattern: ['images'],
    create: (sources, config) => {
      return () => {
        return gulp.src(sources)
          .pipe(plugins.plumber())
          .pipe(gulp.dest(paths.dist + config.path))
          .pipe(server.notify.apply(server));
      }
    }
  },{
    shortName: 'fonts',
    sourcePattern: ['fonts'],
    destination: '/fonts',
    create: (sources, config) => {
      return () => {
        return gulp.src(sources)
          .pipe(plugins.plumber())
          .pipe(gulp.dest(paths.dist + config.path + config.destination))
          .pipe(server.notify.apply(server));
      }
    }
  },{
    shortName: 'sass',
    sourcePattern: ['sass'],
    destination: '/bundle.css',
    create: (sources, config) => {
      return () => {
        return gulp.src(sources)
          .pipe(plugins.plumber())
          .pipe(plugins.concat(config.destination))
          .pipe(plugins.sass({
            outputStyle: 'compressed',
            includePaths: paths.modules
          }))
          .pipe(plugins.autoprefixer())
          .pipe(gulp.dest(paths.dist + config.path))
          .pipe(server.notify.apply(server));
      }
    }
  },{
    shortName: 'js-babel',
    sourcePattern: ['js'],
    destination: '/bundle.js',
    create: (sources, config) => {
      return () => {
        return gulp.src(sources)
          .pipe(plugins.plumber())
          .pipe(plugins.concat(config.destination))
          .pipe(gulp.dest(paths.dist + config.path))
          .pipe(babel({
            presets: ['es2015'],
            babelrc: false
          }))
          .pipe(plugins.uglify())
          .pipe(gulp.dest(paths.dist + config.path))
          .pipe(server.notify.apply(server));
      }
    }
  },{
    shortName: 'js-babel-imports',
    sourcePattern: ['js'], // since there are entry points, this is only what to watch
    create: (sources, config) => {
      return () => {
        // default entry point
        var entryPoints = config.entryPoints || [ 'app.js' ];
        entryPoints = _.map(entryPoints, (entry) => { return paths.app + config.path +'/'+ entry; });
        _.each(entryPoints, (entry) => {
          const destination = entry.split(config.path +'/')[1]; // get the filename from the full path
          return browserify({
            entries: entry, // full path
            extensions: ['.js','.jsx']
          })
          .transform(babelify, {presets: ['es2015']})
          .bundle()
          .pipe(plugins.plumber())
          .pipe(source(destination)) // just the filename
          .pipe(gulp.dest(paths.dist + config.path)) // just the path
          .pipe(server.notify.apply(server));
        });
      }
    }
  },{
    shortName: 'js-react',
    sourcePattern: ['js'], // since there are entry points, this is only what to watch
    create: (sources, config) => {
      return () => {
        // default entry point
        var entryPoints = config.entryPoints || [ 'app.js' ];
        entryPoints = _.map(entryPoints, (entry) => { return paths.app + config.path +'/'+ entry; });
        _.each(entryPoints, (entry) => {
          const destination = entry.split(config.path +'/')[1]; // get the filename from the full path
          return browserify({
            entries: entry, // full path
            extensions: ['.js','.jsx']
          })
          .transform(babelify, {presets: ['es2015', 'react']})
          .bundle()
          .pipe(plugins.plumber())
          .pipe(source(destination)) // just the filename
          .pipe(gulp.dest(paths.dist + config.path)) // just the path
          .pipe(server.notify.apply(server));
        });
      }
    }
  }
];

module.exports = {taskList, sourcePatterns};
<|endoftext|>


<commit_before>// Load in our dependencies
var fs = require('fs');
var path = require('path');
var mkdirp = require('mkdirp');
var tmp = require('tmp');
var looksSameComparator = require('./image-comparators/looks-same');
var logger = require('./logger');

// Define our constructor
function ImageSet(currentImg, refImg, options) {
  // Save our options for later
  this.currentImg = currentImg;
  this.refImg = refImg;
  this.diffImg = options.diffImg || null;
  // TODO: Define `imagesEqual` here as well as set `imagesEqual` in `diff`

  // Log image set generation
  logger.verbose.info('Image set generated with current image "' + this.currentImg + '", ' +
    'reference image "' + this.refImg + '", and diff image "' + this.diffImg + '"');
}

// Define class methods/properties
ImageSet.generateSets = function (currentImgArr, refImgArr, options) {
  // Verify we have equal sets of images
  if (currentImgArr.length !== refImgArr.length) {
    throw new Error(currentImgArr.length + ' current images ' +
      'and ' + refImgArr.length + ' reference images were received. We expect these numbers to line up, ' +
      'please check path resolution (it\'s possible reference image doesn\'t exist yet as it\'s new)');
  }

  // If there are diff images, then verify they are equal as well
  var diffImgArr = options.diffImages;
  if (diffImgArr) {
    if (diffImgArr.length !== currentImgArr.length) {
      throw new Error(diffImgArr.length + ' diff images ' +
        'and ' + currentImgArr.length + ' current images were received. We expect these numbers to line up, ' +
        'please check path resolution (it\'s possible diff image doesn\'t exist yet as it\'s new)');
    }
  }

  // Generate new sets of images
  var imageSets = currentImgArr.map(function createImgSet (currentImg, i) {
    return new ImageSet(currentImg, refImgArr[i], {
      diffImg: diffImgArr ? diffImgArr[i] : null
    });
  });

  // Return our image sets
  return imageSets;
};
ImageSet.getTemporaryDirectory = function () {
  // If we haven't set up a temporary directory, create one now
  var cls = this;
  if (!cls.temporaryDirectory) {
    // DEV: We use blocking creation to prevent multiple directories being created in parallel
    cls.temporaryDirectory = tmp.dirSync();
    logger.verbose.info('Created temporary directory "' + cls.temporaryDirectory.name + '"');
  }

  // Return our temporary directory
  return cls.temporaryDirectory.name;
};

// Define instance methods
ImageSet.prototype = {
  compare: function (cb) {
    // If there is no diff image path yet
    var that = this;
    if (!this.diffImg) {
      // Get our temporary directory
      var temporaryDirectory = ImageSet.getTemporaryDirectory();

      // Resolve and save our filepath to the diff
      // https://nodejs.org/api/path.html#path_path_extname_path
      // `path/to/Chrome~current.png` -> `/tmp/123456/path/to/Chrome~current.png.diff.png`
      // DEV: We use `current` as we assume these are volatile whereas `ref` could be in version control
      var ext = path.extname(this.currentImg); // .png
      this.diffImg = path.join(temporaryDirectory, this.currentImg + '.diff' + ext);

      // Create our diff image's directory
      mkdirp(path.dirname(this.diffImg), function handleMkdirp (err) {
        // If there was an error, callback with it
        if (err) {
          return cb(err);
        }

        // Otherwise, continue
        next();
      });
    // Otherwise, in a split second continue to next part
    } else {
      // DEV: We use `setImmediate` to avoid zalgo (i.e. we expect async behavior)
      setImmediate(next);
    }

    function next() { // jshint ignore:line
      // Compare our imges
      // https://github.com/gemini-testing/gemini/blob/v4.13.0/lib/image/index.js#L79-L104
      // TODO: Support multiple image comparators (e.g. `image-diff`)
      looksSameComparator(that, cb);
    }
  },
  // DEV: We could use `toJSON` but there is a trust issue of it always being used/not
  serialize: function () {
    return {
      id: this.refImg,
      currentImgUrl: '/images/' + encodeURIComponent(this.currentImg),
      diffImgUrl: '/images/' + encodeURIComponent(this.diffImg),
      refImgUrl: '/images/' + encodeURIComponent(this.refImg),
      imagesEqual: this.imagesEqual
    };
  },
  updateRef: function (refBuff, cb) {
    // Update our reference image
    // DEV: We could load a single file handler at start of application and persist it to avoid path shifting hacks
    //   but we shouldn't be run as root so we should be fine
    var that = this;
    fs.writeFile(this.refImg, refBuff, function handleWriteFile (err) {
      // If there was an error, callback with it
      if (err) {
        return cb(err);
      }

      // Otherwise, compare our images and callback with that result
      // DEV: We run this so we update our diff image
      that.compare(cb);
    });
  }
};

// Export our constructor
module.exports = ImageSet;
<commit_msg>Relocated mkdirp for diff screenshots to better support custom paths<commit_after>// Load in our dependencies
var fs = require('fs');
var path = require('path');
var mkdirp = require('mkdirp');
var tmp = require('tmp');
var looksSameComparator = require('./image-comparators/looks-same');
var logger = require('./logger');

// Define our constructor
function ImageSet(currentImg, refImg, options) {
  // Save our options for later
  this.currentImg = currentImg;
  this.refImg = refImg;
  this.diffImg = options.diffImg || null;
  // TODO: Define `imagesEqual` here as well as set `imagesEqual` in `diff`

  // Log image set generation
  logger.verbose.info('Image set generated with current image "' + this.currentImg + '", ' +
    'reference image "' + this.refImg + '", and diff image "' + this.diffImg + '"');
}

// Define class methods/properties
ImageSet.generateSets = function (currentImgArr, refImgArr, options) {
  // Verify we have equal sets of images
  if (currentImgArr.length !== refImgArr.length) {
    throw new Error(currentImgArr.length + ' current images ' +
      'and ' + refImgArr.length + ' reference images were received. We expect these numbers to line up, ' +
      'please check path resolution (it\'s possible reference image doesn\'t exist yet as it\'s new)');
  }

  // If there are diff images, then verify they are equal as well
  var diffImgArr = options.diffImages;
  if (diffImgArr) {
    if (diffImgArr.length !== currentImgArr.length) {
      throw new Error(diffImgArr.length + ' diff images ' +
        'and ' + currentImgArr.length + ' current images were received. We expect these numbers to line up, ' +
        'please check path resolution (it\'s possible diff image doesn\'t exist yet as it\'s new)');
    }
  }

  // Generate new sets of images
  var imageSets = currentImgArr.map(function createImgSet (currentImg, i) {
    return new ImageSet(currentImg, refImgArr[i], {
      diffImg: diffImgArr ? diffImgArr[i] : null
    });
  });

  // Return our image sets
  return imageSets;
};
ImageSet.getTemporaryDirectory = function () {
  // If we haven't set up a temporary directory, create one now
  var cls = this;
  if (!cls.temporaryDirectory) {
    // DEV: We use blocking creation to prevent multiple directories being created in parallel
    cls.temporaryDirectory = tmp.dirSync();
    logger.verbose.info('Created temporary directory "' + cls.temporaryDirectory.name + '"');
  }

  // Return our temporary directory
  return cls.temporaryDirectory.name;
};

// Define instance methods
ImageSet.prototype = {
  compare: function (cb) {
    // If there is no diff image path yet
    var that = this;
    if (!this.diffImg) {
      // Get our temporary directory
      var temporaryDirectory = ImageSet.getTemporaryDirectory();

      // Resolve and save our filepath to the diff
      // https://nodejs.org/api/path.html#path_path_extname_path
      // `path/to/Chrome~current.png` -> `/tmp/123456/path/to/Chrome~current.png.diff.png`
      // DEV: We use `current` as we assume these are volatile whereas `ref` could be in version control
      var ext = path.extname(this.currentImg); // .png
      this.diffImg = path.join(temporaryDirectory, this.currentImg + '.diff' + ext);
    // Otherwise, in a split second continue to next part
    } else {
      // DEV: We use `setImmediate` to avoid zalgo (i.e. we expect async behavior)
      setImmediate(next);
    }

    function next() { // jshint ignore:line
      // Create our diff image's directory
      // DEV: We could have a custom diff path that doesn't yet exist
      mkdirp(path.dirname(that.diffImg), function handleMkdirp (err) {
        // If there was an error, callback with it
        if (err) {
          return cb(err);
        }

        // Compare our imges
        // https://github.com/gemini-testing/gemini/blob/v4.13.0/lib/image/index.js#L79-L104
        // TODO: Support multiple image comparators (e.g. `image-diff`)
        looksSameComparator(that, cb);
      });
    }
  },
  // DEV: We could use `toJSON` but there is a trust issue of it always being used/not
  serialize: function () {
    return {
      id: this.refImg,
      currentImgUrl: '/images/' + encodeURIComponent(this.currentImg),
      diffImgUrl: '/images/' + encodeURIComponent(this.diffImg),
      refImgUrl: '/images/' + encodeURIComponent(this.refImg),
      imagesEqual: this.imagesEqual
    };
  },
  updateRef: function (refBuff, cb) {
    // Update our reference image
    // DEV: We could load a single file handler at start of application and persist it to avoid path shifting hacks
    //   but we shouldn't be run as root so we should be fine
    var that = this;
    fs.writeFile(this.refImg, refBuff, function handleWriteFile (err) {
      // If there was an error, callback with it
      if (err) {
        return cb(err);
      }

      // Otherwise, compare our images and callback with that result
      // DEV: We run this so we update our diff image
      that.compare(cb);
    });
  }
};

// Export our constructor
module.exports = ImageSet;
<|endoftext|>


<commit_before>'use strict'
module.exports = function (newDoc, oldDoc, userCtx, secObj) {
  const ouch = (err) => { throw err }

  const adminDelete = newDoc._deleted && userCtx.roles.indexOf('_admin') -== -1
  if (userCtx.roles.indexOf('teacher') === -1 && userCtx.roles.indexOf('_admin') === -1) { ouch({ unauthorized: 'not a teacher' }) }
  if (!adminDelete && userCtx.name !== newDoc.creator) { ouch({ forbidden: 'wrong creator' }) }
  if (!adminDelete && !newDoc.createdAt) { ouch({ forbidden: 'missing field: createdAt' }) }
  if (!adminDelete && oldDoc) {
    if (!newDoc.updatedAt) { ouch({ forbidden: 'missing field: updatedAt' }) }
    if (oldDoc.updatedAt >= newDoc.updatedAt) { ouch({ forbidden: 'field updatedAt went back in time' }) }
  }
}
<commit_msg>Allow admin to delete stuff (bugfix).<commit_after>'use strict'
module.exports = function (newDoc, oldDoc, userCtx, secObj) {
  const ouch = (err) => { throw err }

  const adminDelete = newDoc._deleted && userCtx.roles.indexOf('_admin') !== -1
  if (userCtx.roles.indexOf('teacher') === -1 && userCtx.roles.indexOf('_admin') === -1) { ouch({ unauthorized: 'not a teacher' }) }
  if (!adminDelete && userCtx.name !== newDoc.creator) { ouch({ forbidden: 'wrong creator' }) }
  if (!adminDelete && !newDoc.createdAt) { ouch({ forbidden: 'missing field: createdAt' }) }
  if (!adminDelete && oldDoc) {
    if (!newDoc.updatedAt) { ouch({ forbidden: 'missing field: updatedAt' }) }
    if (oldDoc.updatedAt >= newDoc.updatedAt) { ouch({ forbidden: 'field updatedAt went back in time' }) }
  }
}
<|endoftext|>


<commit_before>'use strict';
var Agent = require('./agent');
var EventEmitter = require('events').EventEmitter;
var fs = require('fs');
var path = require('path');
var persistenceName = '.mrpersistent';
var processor = require('./processor');
var request = require('./request');
var shared = require('../shared');
var utilities = require('./utilities');

/**
 * Runs the tasks in a queue and returns an event emitter.
 * @param {!Array.<!{address: string, options: !IOptions}>} tasks
 * @param {number=} maximum
 * @return {!IEmitter}
 */
module.exports = function(tasks, maximum) {
  var beginTimeInMs = Date.now();
  var emitter = new EventEmitter();
  var queue = new shared.common.Queue(maximum, function(task, done) {
    if (task.alias) return taskChapter(emitter, task, done);
    taskSeries(emitter, queue, task, done);
  }, function(err) {
    save(queue.save, function(saveErr) {
      if (err || saveErr) return emitter.emit('error', err || saveErr);
      emitter.emit('end', {timeInMs: Date.now() - beginTimeInMs});
    });
  });
  emitter.push = function(tasks) {
    queue.push(tasks);
  };
  process.nextTick(function() {
    queue.save = {};
    queue.push(tasks);
  });
  return emitter;
};

/**
 * Creates an agent.
 * @param {!Object} task
 * @param {function(Error, Agent)} done
 */
function createAgent(task, done) {
  processor(task.options, task.series, task.chapter, function(err, processors) {
    if (err) return done(err);
    var filePath = task.filePath;
    var Meta = shared.publisher.Meta;
    done(undefined, task.options.meta ?
      new Agent(filePath, processors) :
      new Agent(filePath, processors, new Meta(task.series, task.chapter)));
  });
}

/**
 * Enqueues the chapter.
 * @param {!IEmitter} emitter
 * @param {!Queue} queue
 * @param {!Object} task
 * @param {!IChapter} chapter
 * @param {function(Error)} done
 */
function enqueueChapter(emitter, queue, task, chapter, done) {
  if (utilities.excluded(task.options, chapter)) return process.nextTick(done);
  var alias = shared.common.alias(task.series, chapter, task.options.extension);
  if (!alias) return process.nextTick(done);
  var filePath = path.join(task.options.output || '', alias);
  if (enqueueChapterPersist(emitter, task, chapter, done)) return;
  utilities.duplicate(task.options, filePath, function(duplicate) {
    if (!duplicate) {
      queue.push({
        alias: alias,
        chapter: chapter,
        filePath: filePath,
        options: task.options,
        persistent: task.persistent,
        series: task.series
      });
    }
    done();
  });
}

/**
 * Checks and matches enqueuing chapter persistence records.
 * @param {!IEmitter} emitter
 * @param {!Object} task
 * @param {!IChapter} chapter
 * @param {function(Error)} done
 * @return {boolean}
 */
function enqueueChapterPersist(emitter, task, chapter, done) {
  var alias = shared.common.alias(task.series, chapter, task.options.extension);
  var identifier = String(chapter.identifier);
  if (typeof task.persistent[identifier] !== 'undefined') {
    var basename = path.basename(alias);
    if (basename === task.persistent[identifier]) {
      process.nextTick(done);
      return true;
    }
    var output = task.options.output || '';
    var title = shared.common.alias.invalidate(task.series.title);
    var previousPath = path.join(output, title, task.persistent[identifier]);
    var currentPath = path.join(task.options.output || '', alias);
    task.persistent[identifier] = basename;
    emitter.emit('data', {item: basename, type: 'switched'});
    fs.rename(previousPath, currentPath, done);
    return true;
  }
  return false;
}

/**
 * Enqueues the series.
 * @param {!IEmitter} emitter
 * @param {!Queue} queue
 * @param {!Object} task
 * @param {function(Error)} done
 */
function enqueueSeries(emitter, queue, task, done) {
  request(task.series, 'utf8', function(err) {
    if (err) return done(err);
    var title = shared.common.alias.invalidate(task.series.title);
    var filePath = path.join(task.options.output || '', title, persistenceName);
    fs.exists(filePath, function(exists) {
      if (!exists) {
        task.persistent = queue.save[filePath] = {};
        return enqueueSeriesNext(emitter, queue, task, done);
      }
      fs.readFile(filePath, 'utf8', function(err, contents) {
        if (err) return done(err);
        task.persistent = queue.save[filePath] = JSON.parse(contents);
        enqueueSeriesNext(emitter, queue, task, done);
      });
    });
  });
}

/**
 * Continues enqueuing the series.
 * @param {!IEmitter} emitter
 * @param {!Queue} queue
 * @param {!Object} task
 * @param {function(Error)} done
 */
function enqueueSeriesNext(emitter, queue, task, done) {
  shared.common.async.eachSeries(task.series.children, function(chapter, next) {
    if (!chapter) return next();
    enqueueChapter(emitter, queue, task, chapter, next);
  }, done);
}

/**
 * Saves each persistent record.
 * @param {!Object.<string, !Object>} records
 * @param {function(Error)} done
 */
function save(records, done) {
  shared.common.async.eachSeries(Object.keys(records), function(key, next) {
    var value = JSON.stringify(records[key], null, '  ');
    fs.writeFile(key, value, 'utf8', function() {
      // IGNORE: The error is ignored. While the error can be valid (due to
      // permissions, for example), it is considered likely to be the result of
      // attempting to write to a folder which has not been created by an agent.
      next();
    });
  }, done);
}

/**
 * Runs a chapter task.
 * @param {!EventEmitter} emitter
 * @param {!Object} task
 * @param {function(Error)} done
 */
function taskChapter(emitter, task, done) {
  var basename = path.basename(task.alias);
  var begin = Date.now();
  emitter.emit('data', {item: basename, type: 'fetching'});
  request(task.chapter, 'utf8', function(err) {
    if (err) return done(err);
    createAgent(task, function(err, agent) {
      if (err) return done(err);
      shared.publisher.mirror(agent, task.series, task.chapter, function(err) {
        if (err) return done(err);
        agent.publish(function(err) {
          if (err) return emitter.emit('error', err);
          if (!agent._disposed && !task.options.persistent) {
            task.persistent[task.chapter.identifier] = basename;
          }
          emitter.emit('data', {
            item: basename,
            timeInMs: Date.now() - begin,
            type: agent._disposed ? 'disposed' : 'finished'
          });
          done();
        });
      });
    });
  });
}

/**
 * Runs a series task.
 * @param {!EventEmitter} emitter
 * @param {!Queue} queue
 * @param {!Object} task
 * @param {function(Error)} done
 */
function taskSeries(emitter, queue, task, done) {
  task.series = shared.provider(task.address);
  if (task.series) return enqueueSeries(emitter, queue, task, done);
  emitter.emit('data', {item: task.address, type: 'obscured'});
  process.nextTick(done);
}
<commit_msg>Normalize naming.<commit_after>'use strict';
var Agent = require('./agent');
var EventEmitter = require('events').EventEmitter;
var fs = require('fs');
var path = require('path');
var persistenceName = '.mrpersistent';
var processor = require('./processor');
var request = require('./request');
var shared = require('../shared');
var utilities = require('./utilities');

/**
 * Runs the tasks in a queue and returns an event emitter.
 * @param {!Array.<!{address: string, options: !IOptions}>} tasks
 * @param {number=} maximum
 * @return {!IEmitter}
 */
module.exports = function(tasks, maximum) {
  var beginTimeInMs = Date.now();
  var emitter = new EventEmitter();
  var queue = new shared.common.Queue(maximum, function(task, done) {
    if (task.alias) return taskChapter(emitter, task, done);
    taskSeries(emitter, queue, task, done);
  }, function(err) {
    save(queue.save, function(saveErr) {
      if (err || saveErr) return emitter.emit('error', err || saveErr);
      emitter.emit('end', {timeInMs: Date.now() - beginTimeInMs});
    });
  });
  emitter.push = function(tasks) {
    queue.push(tasks);
  };
  process.nextTick(function() {
    queue.save = {};
    queue.push(tasks);
  });
  return emitter;
};

/**
 * Creates an agent.
 * @param {!Object} task
 * @param {function(Error, Agent)} done
 */
function createAgent(task, done) {
  processor(task.options, task.series, task.chapter, function(err, processors) {
    if (err) return done(err);
    var filePath = task.filePath;
    var Meta = shared.publisher.Meta;
    done(undefined, task.options.meta ?
      new Agent(filePath, processors) :
      new Agent(filePath, processors, new Meta(task.series, task.chapter)));
  });
}

/**
 * Enqueues the chapter.
 * @param {!IEmitter} emitter
 * @param {!Queue} queue
 * @param {!Object} task
 * @param {!IChapter} chapter
 * @param {function(Error)} done
 */
function enqueueChapter(emitter, queue, task, chapter, done) {
  if (utilities.excluded(task.options, chapter)) return process.nextTick(done);
  var alias = shared.common.alias(task.series, chapter, task.options.extension);
  if (!alias) return process.nextTick(done);
  var filePath = path.join(task.options.output || '', alias);
  if (enqueueChapterPersistent(emitter, task, chapter, done)) return;
  utilities.duplicate(task.options, filePath, function(duplicate) {
    if (!duplicate) {
      queue.push({
        alias: alias,
        chapter: chapter,
        filePath: filePath,
        options: task.options,
        persistent: task.persistent,
        series: task.series
      });
    }
    done();
  });
}

/**
 * Checks and matches enqueuing chapter persistence records.
 * @param {!IEmitter} emitter
 * @param {!Object} task
 * @param {!IChapter} chapter
 * @param {function(Error)} done
 * @return {boolean}
 */
function enqueueChapterPersistent(emitter, task, chapter, done) {
  var alias = shared.common.alias(task.series, chapter, task.options.extension);
  var identifier = String(chapter.identifier);
  if (typeof task.persistent[identifier] !== 'undefined') {
    var basename = path.basename(alias);
    if (basename === task.persistent[identifier]) {
      process.nextTick(done);
      return true;
    }
    var output = task.options.output || '';
    var title = shared.common.alias.invalidate(task.series.title);
    var previousPath = path.join(output, title, task.persistent[identifier]);
    var currentPath = path.join(task.options.output || '', alias);
    task.persistent[identifier] = basename;
    emitter.emit('data', {item: basename, type: 'switched'});
    fs.rename(previousPath, currentPath, done);
    return true;
  }
  return false;
}

/**
 * Enqueues the series.
 * @param {!IEmitter} emitter
 * @param {!Queue} queue
 * @param {!Object} task
 * @param {function(Error)} done
 */
function enqueueSeries(emitter, queue, task, done) {
  request(task.series, 'utf8', function(err) {
    if (err) return done(err);
    var title = shared.common.alias.invalidate(task.series.title);
    var filePath = path.join(task.options.output || '', title, persistenceName);
    fs.exists(filePath, function(exists) {
      if (!exists) {
        task.persistent = queue.save[filePath] = {};
        return enqueueSeriesNext(emitter, queue, task, done);
      }
      fs.readFile(filePath, 'utf8', function(err, contents) {
        if (err) return done(err);
        task.persistent = queue.save[filePath] = JSON.parse(contents);
        enqueueSeriesNext(emitter, queue, task, done);
      });
    });
  });
}

/**
 * Continues enqueuing the series.
 * @param {!IEmitter} emitter
 * @param {!Queue} queue
 * @param {!Object} task
 * @param {function(Error)} done
 */
function enqueueSeriesNext(emitter, queue, task, done) {
  shared.common.async.eachSeries(task.series.children, function(chapter, next) {
    if (!chapter) return next();
    enqueueChapter(emitter, queue, task, chapter, next);
  }, done);
}

/**
 * Saves each persistent record.
 * @param {!Object.<string, !Object>} records
 * @param {function(Error)} done
 */
function save(records, done) {
  shared.common.async.eachSeries(Object.keys(records), function(key, next) {
    var value = JSON.stringify(records[key], null, '  ');
    fs.writeFile(key, value, 'utf8', function() {
      // IGNORE: The error is ignored. While the error can be valid (due to
      // permissions, for example), it is considered likely to be the result of
      // attempting to write to a folder which has not been created by an agent.
      next();
    });
  }, done);
}

/**
 * Runs a chapter task.
 * @param {!EventEmitter} emitter
 * @param {!Object} task
 * @param {function(Error)} done
 */
function taskChapter(emitter, task, done) {
  var basename = path.basename(task.alias);
  var begin = Date.now();
  emitter.emit('data', {item: basename, type: 'fetching'});
  request(task.chapter, 'utf8', function(err) {
    if (err) return done(err);
    createAgent(task, function(err, agent) {
      if (err) return done(err);
      shared.publisher.mirror(agent, task.series, task.chapter, function(err) {
        if (err) return done(err);
        agent.publish(function(err) {
          if (err) return emitter.emit('error', err);
          if (!agent._disposed && !task.options.persistent) {
            task.persistent[task.chapter.identifier] = basename;
          }
          emitter.emit('data', {
            item: basename,
            timeInMs: Date.now() - begin,
            type: agent._disposed ? 'disposed' : 'finished'
          });
          done();
        });
      });
    });
  });
}

/**
 * Runs a series task.
 * @param {!EventEmitter} emitter
 * @param {!Queue} queue
 * @param {!Object} task
 * @param {function(Error)} done
 */
function taskSeries(emitter, queue, task, done) {
  task.series = shared.provider(task.address);
  if (task.series) return enqueueSeries(emitter, queue, task, done);
  emitter.emit('data', {item: task.address, type: 'obscured'});
  process.nextTick(done);
}
<|endoftext|>


<commit_before>var stream = require("stream");
var util = require("util");
var async = require("async");

function isInBbox(position, bbox) {
	if(position.lat > bbox.top || position.lat < bbox.bottom)
		return false;
	if(bbox.right < bbox.left) // bbox spans over lon = 180
		return (position.lon > bbox.left || position.lon < bbox.right);
	else
		return (position.lon > bbox.left && position.lon < bbox.right);
}

function filterStream(inStream, filterFunction) {
	var ret = new stream.Readable({ objectMode: true });
	inStream.on("data", function(data) {
		var data = filterFunction(data);
		if(data != null)
			ret.push(data);
	}).on("end", function() {
		ret.push();
	}).on("error", function(err) {
		ret.emit("error", err);
	});

	ret._read = function() {
	};

	return ret;
}

function filterStreamAsync(inStream, filterFunction) {
	var error = false;

	var ret = new stream.Readable({ objectMode: true });

	var running = false;
	var queue = [ ];

	function handleQueue() {
		if(error || running)
			return;

		if(queue.length > 0) {
			var next = queue.shift();
			if(next == null) {
				ret.push();
			} else {
				running = true;
				filterFunction(next, function(err, newData) {
					running = false;

					if(error)
						return;

					if(err) {
						error = true;
						ret.emit("error", err);
					} else if(newData != null) {
						ret.push(newData);
					}

					setImmediate(handleQueue);
				});
			}
		}
	}

	inStream.on("data", function(data) {
		if(data != null)
			queue.push(data);
		handleQueue();
	}).on("end", function() {
		queue.push(null);
		handleQueue();
	}).on("error", function(err) {
		ret.emit("error", err);
	});

	ret._read = function() {
	};

	return ret;
}

function extend(obj1, obj2) {
	if(obj1 == null)
		return null;

	for(var i=1; i<arguments.length; i++) {
		if(arguments[i] == null)
			continue;

		for(var j in arguments[i]) {
			obj1[j] = arguments[i][j];
		}
	}
	return obj1;
}

function calculateDistance(posList) {
	// From http://stackoverflow.com/a/365853/242365

	var R = 6371; // km
	var ret = 0;

	for(var i=1; i<posList.length; i++) {
		var lat1 = posList[i-1].lat * Math.PI / 180;
		var lon1 = posList[i-1].lon * Math.PI / 180;
		var lat2 = posList[i].lat * Math.PI / 180;
		var lon2 = posList[i].lon * Math.PI / 180;
		var dLat = lat2-lat1;
		var dLon = lon2-lon1;

		var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
		        Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
		var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
		ret += R * c;
	}

	return ret;
}

var LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

function generateRandomId(length) {
	var randomPadId = "";
	for(var i=0; i<length; i++) {
		randomPadId += LETTERS[Math.floor(Math.random() * LETTERS.length)];
	}
	return randomPadId;
}

var FAILURE = { };

function stripObject(obj, structure) {
	return _stripObject(obj, structure) !== FAILURE;
}

function _stripObject(obj, type) {
	if(obj === undefined)
		return obj;
	else if(obj === null)
		return obj;
	else if(type instanceof Array) {
		if(!(obj instanceof Array))
			return FAILURE;

		for(var i=0; i<obj.length; i++) {
			if((obj[i] = _stripObject(obj[i], type[0])) === FAILURE)
				return FAILURE;
		}
		return obj;
	}
	else if(typeof type == "function")
		return (obj instanceof type) ? obj : FAILURE;
	else if(type instanceof Object) {
		if(!(obj instanceof Object))
			return FAILURE;

		for(var i in obj) {
			if(type[i] == null || obj[i] === undefined)
				delete obj[i];
			else if((obj[i] = _stripObject(obj[i], type[i])) === FAILURE)
				return FAILURE;
		}
		return obj;
	}
	else if(type == "number" && typeof obj == "string")
		return isNaN(obj = 1*obj) ? FAILURE : obj;
	else if(type == "string" && typeof obj == "number")
		return ""+obj;
	else if(typeof type == "string")
		return (typeof obj == type) ? obj : FAILURE;
	else
		return FAILURE;
}

function ArrayStream(array) {
	stream.Readable.call(this, { objectMode: true });

	if(array != null)
		setImmediate(function(){ this.receiveArray(null, array); }.bind(this));
}

util.inherits(ArrayStream, stream.Readable);

ArrayStream.prototype._read = function(size) {
};

ArrayStream.prototype.receiveArray = function(err, array) {
	if(err)
		return this.emit("error", err);

	for(var i=0; i<array.length; i++)
		this.push(array[i]);
	this.push(null);
};

function asyncStreamEach(stream, handle, callback) {
	var queue = async.queue(handle, 5);

	var push = function() {
		if(queue.length() == 0) {
			var item = stream.read();
			if(item != null)
				queue.push(item, push);
		}
	};

	stream.on("readable", push);

	stream.on("end", function() {
		if(queue.running() == 0 && queue.length() == 0)
			callback();
		else
			queue.drain = callback;
	});

	stream.on("error", function(err) {
		queue.kill();
		callback(err);
	});
}

function escapeXml(str) {
	return ("" + str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}

function isoDate(date) {
	if(!date)
		date = new Date();

	function pad(number, length) {
		number = "" + number;
		while(number.length < length)
			number = "0" + number;
		return number;
	}

	return pad(date.getUTCFullYear(), 4) + '-' + pad(date.getUTCMonth()+1, 2) + '-' + pad(date.getUTCDate(), 2) + 'T' + pad(date.getUTCHours(), 2) + ':' + pad(date.getUTCMinutes(), 2) + ':' + pad(date.getUTCSeconds(), 2) + 'Z';
}

module.exports = {
	isInBbox : isInBbox,
	filterStream : filterStream,
	filterStreamAsync : filterStreamAsync,
	extend : extend,
	calculateDistance : calculateDistance,
	generateRandomId : generateRandomId,
	stripObject : stripObject,
	ArrayStream : ArrayStream,
	asyncStreamEach : asyncStreamEach,
	escapeXml : escapeXml,
	isoDate : isoDate
};<commit_msg>Fix stream incompatibility with newer node versions<commit_after>var stream = require("stream");
var util = require("util");
var async = require("async");

function isInBbox(position, bbox) {
	if(position.lat > bbox.top || position.lat < bbox.bottom)
		return false;
	if(bbox.right < bbox.left) // bbox spans over lon = 180
		return (position.lon > bbox.left || position.lon < bbox.right);
	else
		return (position.lon > bbox.left && position.lon < bbox.right);
}

function filterStream(inStream, filterFunction) {
	var ret = new stream.Readable({ objectMode: true });
	inStream.on("data", function(data) {
		var data = filterFunction(data);
		if(data != null)
			ret.push(data);
	}).on("end", function() {
		ret.push();
	}).on("error", function(err) {
		ret.emit("error", err);
	});

	ret._read = function() {
	};

	return ret;
}

function filterStreamAsync(inStream, filterFunction) {
	var error = false;

	var ret = new stream.Readable({ objectMode: true });

	var running = false;
	var queue = [ ];

	function handleQueue() {
		if(error || running)
			return;

		if(queue.length > 0) {
			var next = queue.shift();
			if(next == null) {
				ret.push(null);
			} else {
				running = true;
				filterFunction(next, function(err, newData) {
					running = false;

					if(error)
						return;

					if(err) {
						error = true;
						ret.emit("error", err);
					} else if(newData != null) {
						ret.push(newData);
					}

					setImmediate(handleQueue);
				});
			}
		}
	}

	inStream.on("data", function(data) {
		if(data != null)
			queue.push(data);
		handleQueue();
	}).on("end", function() {
		queue.push(null);
		handleQueue();
	}).on("error", function(err) {
		ret.emit("error", err);
	});

	ret._read = function() {
	};

	return ret;
}

function extend(obj1, obj2) {
	if(obj1 == null)
		return null;

	for(var i=1; i<arguments.length; i++) {
		if(arguments[i] == null)
			continue;

		for(var j in arguments[i]) {
			obj1[j] = arguments[i][j];
		}
	}
	return obj1;
}

function calculateDistance(posList) {
	// From http://stackoverflow.com/a/365853/242365

	var R = 6371; // km
	var ret = 0;

	for(var i=1; i<posList.length; i++) {
		var lat1 = posList[i-1].lat * Math.PI / 180;
		var lon1 = posList[i-1].lon * Math.PI / 180;
		var lat2 = posList[i].lat * Math.PI / 180;
		var lon2 = posList[i].lon * Math.PI / 180;
		var dLat = lat2-lat1;
		var dLon = lon2-lon1;

		var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
		        Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
		var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
		ret += R * c;
	}

	return ret;
}

var LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

function generateRandomId(length) {
	var randomPadId = "";
	for(var i=0; i<length; i++) {
		randomPadId += LETTERS[Math.floor(Math.random() * LETTERS.length)];
	}
	return randomPadId;
}

var FAILURE = { };

function stripObject(obj, structure) {
	return _stripObject(obj, structure) !== FAILURE;
}

function _stripObject(obj, type) {
	if(obj === undefined)
		return obj;
	else if(obj === null)
		return obj;
	else if(type instanceof Array) {
		if(!(obj instanceof Array))
			return FAILURE;

		for(var i=0; i<obj.length; i++) {
			if((obj[i] = _stripObject(obj[i], type[0])) === FAILURE)
				return FAILURE;
		}
		return obj;
	}
	else if(typeof type == "function")
		return (obj instanceof type) ? obj : FAILURE;
	else if(type instanceof Object) {
		if(!(obj instanceof Object))
			return FAILURE;

		for(var i in obj) {
			if(type[i] == null || obj[i] === undefined)
				delete obj[i];
			else if((obj[i] = _stripObject(obj[i], type[i])) === FAILURE)
				return FAILURE;
		}
		return obj;
	}
	else if(type == "number" && typeof obj == "string")
		return isNaN(obj = 1*obj) ? FAILURE : obj;
	else if(type == "string" && typeof obj == "number")
		return ""+obj;
	else if(typeof type == "string")
		return (typeof obj == type) ? obj : FAILURE;
	else
		return FAILURE;
}

function ArrayStream(array) {
	stream.Readable.call(this, { objectMode: true });

	if(array != null)
		setImmediate(function(){ this.receiveArray(null, array); }.bind(this));
}

util.inherits(ArrayStream, stream.Readable);

ArrayStream.prototype._read = function(size) {
};

ArrayStream.prototype.receiveArray = function(err, array) {
	if(err)
		return this.emit("error", err);

	for(var i=0; i<array.length; i++)
		this.push(array[i]);
	this.push(null);
};

function asyncStreamEach(stream, handle, callback) {
	var queue = async.queue(handle, 5);

	var push = function() {
		if(queue.length() == 0) {
			var item = stream.read();
			if(item != null)
				queue.push(item, push);
		}
	};

	stream.on("readable", push);

	stream.on("end", function() {
		if(queue.running() == 0 && queue.length() == 0)
			callback();
		else
			queue.drain = callback;
	});

	stream.on("error", function(err) {
		queue.kill();
		callback(err);
	});
}

function escapeXml(str) {
	return ("" + str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}

function isoDate(date) {
	if(!date)
		date = new Date();

	function pad(number, length) {
		number = "" + number;
		while(number.length < length)
			number = "0" + number;
		return number;
	}

	return pad(date.getUTCFullYear(), 4) + '-' + pad(date.getUTCMonth()+1, 2) + '-' + pad(date.getUTCDate(), 2) + 'T' + pad(date.getUTCHours(), 2) + ':' + pad(date.getUTCMinutes(), 2) + ':' + pad(date.getUTCSeconds(), 2) + 'Z';
}

module.exports = {
	isInBbox : isInBbox,
	filterStream : filterStream,
	filterStreamAsync : filterStreamAsync,
	extend : extend,
	calculateDistance : calculateDistance,
	generateRandomId : generateRandomId,
	stripObject : stripObject,
	ArrayStream : ArrayStream,
	asyncStreamEach : asyncStreamEach,
	escapeXml : escapeXml,
	isoDate : isoDate
};<|endoftext|>


<commit_before>'use strict';
let fs = require('fs');
let httpProxy = require('http-proxy');
let nei = require('./nei');

module.exports = function (req, res, options, headers) {
  options = options || {};
  if (typeof options === 'string') options = {target: options};

  options.changeOrigin = true;
  if (options.onload) req.onload = options.onload;

  let server = httpProxy.createServer(function (req, res) {

  });
  let errorFunc = function () {
    return function (err, req, res) {
      // 处理代理错误信息
      if (err && err.code === 'ECONNRESET') return res.send('end');
      else {
        res.writeHead(500, {
          'Content-Type': 'text/plain'
        });
        res.end('代理请求错误，url为' + req.url);
      }
    }
  }
  server.on('error', errorFunc());
  server.on('proxyReq', function (proxyReq) {
    // 追加头信息
    proxyReq.setHeader('X-Special-Proxy-Header', 'JTR');
  });

  headers = nei.getting().API_RES_HEADERS;
  Object.keys(headers).forEach(function (h) {
    res.header(h, headers[h]);
  });

  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
  return server.web(req, res, options);
};
<commit_msg>删除无用代码， 增加charset头字段，防止编码问题<commit_after>'use strict';
let fs = require('fs');
let httpProxy = require('http-proxy');
let nei = require('./nei');

module.exports = function (req, res, options, headers) {
  options = options || {};
  if (typeof options === 'string') options = {target: options};

  options.changeOrigin = true;

  let server = httpProxy.createServer(function (req, res) {

  });
  let errorFunc = function () {
    return function (err, req, res) {
      // 处理代理错误信息
      if (err && err.code === 'ECONNRESET') return res.send('end');
      else {
        res.writeHead(500, {
          'Content-Type': 'text/plain;charset=utf-8',
        });
        res.end('代理请求错误，url为' + req.url);
      }
    }
  }
  server.on('error', errorFunc());
  server.on('proxyReq', function (proxyReq) {
    // 追加头信息
    proxyReq.setHeader('X-Special-Proxy-Header', 'JTR');
  });

  headers = nei.getting().API_RES_HEADERS;
  Object.keys(headers).forEach(function (h) {
    res.header(h, headers[h]);
  });

  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
  return server.web(req, res, options);
};
<|endoftext|>