<commit_before>import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import INITIAL_CONFIG from '../data/editorConfig';
import { SEND_TO_NOTES, FROM_BLANK_NOTE } from '../utils/constants';
import { getPadStats, customizeEditor } from '../utils/editor';

import { updateNote, createNote, deleteNote, setFocusedNote } from '../actions';

const styles = {
  container: {
    flex: '100%',
    display: 'flex',
    flexDirection: 'column'
  }
};

class Editor extends React.Component {
  constructor(props, context) {
    super(props);
    this.props = props;
    this.editor = null; // Editor object
    this.ignoreChange = false;
    this.delayUpdateNote = null;

    this.sendToNoteListener = (eventData) => {
      if (eventData.action === SEND_TO_NOTES) {
        browser.windows.getCurrent({populate: true}).then((windowInfo) => {
          if (windowInfo.id === eventData.windowId) {
            let content = this.editor.getData();
            if (content === '

') content = '';
            this.editor.setData(content + `<p>${eventData.text}</p>`);
          }
        });
      }
    };
  }

  componentDidMount() {

    ClassicEditor.create(this.node, INITIAL_CONFIG)
      .then(editor => {
        this.editor = editor;

        chrome.runtime.onMessage.addListener(this.sendToNoteListener);

        customizeEditor(editor);

        // Focus the text editor
        this.editor.editing.view.focus();

        editor.model.document.on('change', (eventInfo, name) => {
          // Cache update event in case of multi-change event (copy pasting trigger many).
          clearTimeout(this.delayUpdateNote);
          this.delayUpdateNote = setTimeout(() => {

            const isFocused = document
              .querySelector('.ck-editor__editable')
              .classList.contains('ck-focused');
            // Only use the focused editor or handle 'rename' events to set the data into storage.
            if (isFocused || name === 'rename' || name === 'insert') {
                const content = editor.getData();

                if (!this.ignoreChange) {
                  if (content !== '' && content !== '

') {
                    if (!this.props.note.id) {
                      this.props.dispatch(createNote(content, this.props.origin)).then(id => {
                        this.props.dispatch(setFocusedNote(id));
                      });
                    } else {
                      this.props.dispatch(updateNote(this.props.note.id, content));
                    }
                  } else {
                    if (this.props.note.id) {
                      this.props.dispatch(deleteNote(this.props.note.id, FROM_BLANK_NOTE));
                    }
                  }
                }
                this.ignoreChange = false;

                chrome.runtime.sendMessage({
                  action: 'metrics-changed',
                  context: getPadStats(editor)
                });
            }
            this.delayUpdateNote = null;
          }, 50);
        });
      })
      .catch(error => {
        console.error(error); // eslint-disable-line no-console
      });
  }

  // This is triggered when redux update state.
  componentWillReceiveProps(nextProps) {
    if (this.editor && this.props.note &&
        this.editor.getData() !== nextProps.note.content) {
      if (nextProps.note.id !== this.props.note.id) {
        this.ignoreChange = true;
      }
      if (!this.delayUpdateNote) { // If no delay waiting, we apply modification
        this.ignoreChange = true;
        this.editor.setData(nextProps.note.content || '

');
        this.editor.editing.view.focus();
      }
    }
  }

  componentWillUnmount() {
    chrome.runtime.onMessage.removeListener(this.sendToNoteListener);

    if (this.editor) {
      this.editor.destroy();
    }
  }

  render() {
    return (

 {
              this.node = node;
            }}
            dangerouslySetInnerHTML={{ __html: this.props.note ? this.props.note.content : '' }}>

    );
  }
}

// We can reuse notification in editorWrapper using the following :
//

//   <button onClick={this.closeNotification}><CloseIcon /></button>
//
{ browser.i18n.getMessage('maximumPadSizeExceeded') }

//

function mapStateToProps(state) {
  return {
    state
  };
}

Editor.propTypes = {
    state: PropTypes.object.isRequired,
    history: PropTypes.object.isRequired,
    origin: PropTypes.string.isRequired,
    note: PropTypes.object,
    dispatch: PropTypes.func.isRequired
};

export default connect(mapStateToProps)(Editor);
<commit_msg>Fix Add to Notes option is not saved (#1180)<commit_after>import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import INITIAL_CONFIG from '../data/editorConfig';
import { SEND_TO_NOTES, FROM_BLANK_NOTE } from '../utils/constants';
import { getPadStats, customizeEditor } from '../utils/editor';

import { updateNote, createNote, deleteNote, setFocusedNote } from '../actions';

const styles = {
  container: {
    flex: '100%',
    display: 'flex',
    flexDirection: 'column'
  }
};

class Editor extends React.Component {
  constructor(props, context) {
    super(props);
    this.props = props;
    this.editor = null; // Editor object
    this.ignoreChange = false;
    this.delayUpdateNote = null;

    this.sendToNoteListener = (eventData) => {
      if (eventData.action === SEND_TO_NOTES) {
        browser.windows.getCurrent({populate: true}).then((windowInfo) => {
          if (windowInfo.id === eventData.windowId) {
            let content = this.editor.getData();
            if (content === '

') content = '';
            this.editor.setData(content + `<p>${eventData.text}</p>`);
          }
        });
      }
    };
  }

  componentDidMount() {

    ClassicEditor.create(this.node, INITIAL_CONFIG)
      .then(editor => {
        this.editor = editor;

        chrome.runtime.onMessage.addListener(this.sendToNoteListener);

        customizeEditor(editor);

        // Focus the text editor
        this.editor.editing.view.focus();

        editor.model.document.on('change', (eventInfo, name) => {
          // Cache update event in case of multi-change event (copy pasting trigger many).
          clearTimeout(this.delayUpdateNote);
          this.delayUpdateNote = setTimeout(() => {

            const isFocused = document
              .querySelector('.ck-editor__editable')
              .classList.contains('ck-focused');
            // Only use the focused editor or handle 'rename' events to set the data into storage.
            if (isFocused || name === 'rename' || name === 'insert' || name.type && name.type === 'transparent') {
                const content = editor.getData();

                if (!this.ignoreChange) {
                  if (content !== '' && content !== '

') {
                    if (!this.props.note.id) {
                      this.props.dispatch(createNote(content, this.props.origin)).then(id => {
                        this.props.dispatch(setFocusedNote(id));
                      });
                    } else {
                      this.props.dispatch(updateNote(this.props.note.id, content));
                    }
                  } else {
                    if (this.props.note.id) {
                      this.props.dispatch(deleteNote(this.props.note.id, FROM_BLANK_NOTE));
                    }
                  }
                }
                this.ignoreChange = false;

                chrome.runtime.sendMessage({
                  action: 'metrics-changed',
                  context: getPadStats(editor)
                });
            }
            this.delayUpdateNote = null;
          }, 50);
        });
      })
      .catch(error => {
        console.error(error); // eslint-disable-line no-console
      });
  }

  // This is triggered when redux update state.
  componentWillReceiveProps(nextProps) {
    if (this.editor && this.props.note &&
        this.editor.getData() !== nextProps.note.content) {
      if (nextProps.note.id !== this.props.note.id) {
        this.ignoreChange = true;
      }
      if (!this.delayUpdateNote) { // If no delay waiting, we apply modification
        this.ignoreChange = true;
        this.editor.setData(nextProps.note.content || '

');
        this.editor.editing.view.focus();
      }
    }
  }

  componentWillUnmount() {
    chrome.runtime.onMessage.removeListener(this.sendToNoteListener);

    if (this.editor) {
      this.editor.destroy();
    }
  }

  render() {
    return (

 {
              this.node = node;
            }}
            dangerouslySetInnerHTML={{ __html: this.props.note ? this.props.note.content : '' }}>

    );
  }
}

// We can reuse notification in editorWrapper using the following :
//

//   <button onClick={this.closeNotification}><CloseIcon /></button>
//
{ browser.i18n.getMessage('maximumPadSizeExceeded') }

//

function mapStateToProps(state) {
  return {
    state
  };
}

Editor.propTypes = {
    state: PropTypes.object.isRequired,
    history: PropTypes.object.isRequired,
    origin: PropTypes.string.isRequired,
    note: PropTypes.object,
    dispatch: PropTypes.func.isRequired
};

export default connect(mapStateToProps)(Editor);
<|endoftext|>


<commit_before>// TODO: IMPORTANT: rewrite!

/*
 * Scrollable.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
define([
  '../sys',
  '../utils/fps',
  './MoveClip',
  'EaselJS'
],
function(
  sys,
  fps,
  MoveClip,
  createjs
) {
  /**
   * Scrollable Container that Auto-sets borders based on parent container
   *
   * @class Scrollable
   * @memberof dom
   * @extends dom.MoveClip
   *
   * @param {Handlebars} template should be a compiled handlebars template. alternatively can be a plain string or jquery object
   * @param {object} data the data to pass through the template, optional
   * @param {object} options optional options to overwrite container properties before inheriting
   **/
  function Scrollable(template, data, options){
    // extend from base class
    MoveClip.call(this, template, data, options);

    this.borders.x = [0, 0];
    this.borders.y = [0, 0];

    this.fraction.release.x = 0.75;
    this.fraction.release.y = 0.75;

    this.fraction.updateScrollBounds = 1;

    this.__scollTicks = 0;
    this.__scrollTicksInterval = Math.round(createjs.Ticker.getMeasuredFPS() * this.fraction.updateScrollBounds);
    this.__scrollOldSize = {width: 0, height: 0};
    this.__scrollOldParentSize = {width: 0, height: 0};
    this.__removeScrollbarTimeout = null;
    this.autoSetScrollBounds = true;

    this.on('addedToStage', _render, this);
    this.on('removedFromStage', _dispose, this);
  }

  var p = sys.extend(Scrollable, MoveClip);

  /**
   * sets bounds for moveclip based on scrollable parent
   *
   * @method _render
   * @memberof dom.Scrollable
   * @instance
   * @public
   **/
  p.setScrollBounds = function(){
    if (this.parent) {
      var parentSize = {width: this.parent.$el.outerWidth(), height: this.parent.$el.outerHeight()};
      var scrollSize = {width: this.$el.outerWidth(), height: this.$el.outerHeight()};

      if ((this.__scrollOldSize.width === scrollSize.width && this.__scrollOldSize.height === scrollSize.height) &&
        (this.__scrollOldParentSize.width === parentSize.width && this.__scrollOldParentSize.height === parentSize.height)) {
        return;
      }

      var offsetSize = {width: scrollSize.width - parentSize.width, height: scrollSize.height - parentSize.height};
      offsetSize.width = Math.max(0, offsetSize.width);
      offsetSize.height = Math.max(0, offsetSize.height);

      this.borders.x = [0 - offsetSize.width, 0];
      this.borders.y = [0 - offsetSize.height, 0];

      if (this.parent.vScrollbar) {
        var scrollbarHeightOffset = scrollSize.height / parentSize.height;
        scrollbarHeightOffset = Math.sqrt(scrollbarHeightOffset);

        var scrollbarHeight = Math.max(75, Math.min(parentSize.height * (1 / scrollbarHeightOffset)));

        var scrollbarMarginTop = parseFloat(this.parent.vScrollbar.$el.css('margin-top').replace('px', ''));
        if (isNaN(scrollbarMarginTop)) scrollbarMarginTop = 0;

        var scrollbarMarginBottom = parseFloat(this.parent.vScrollbar.$el.css('margin-bottom').replace('px', ''));
        if (isNaN(scrollbarMarginBottom)) scrollbarMarginBottom = 0;

        this.parent.vScrollbar.el.style.height = scrollbarHeight + 'px';
        this.parent.vScrollbar.offset = parentSize.height - scrollbarHeight - scrollbarMarginTop - scrollbarMarginBottom;
      }

      this.elastic.x = offsetSize.width > 0 ? 0.1 : 0;
      this.elastic.y = offsetSize.height > 0 ? 0.1 : 0;

      this.__scrollOldSize = scrollSize;
      this.__scrollOldParentSize = parentSize;
    }
  };

  /**
   * initializes events on Scrollable object
   *
   * @method _render
   * @memberof dom.Scrollable
   * @instance
   * @private
   **/
  var _render = function(){
    this.el.addEventListener('mousewheel', this.__bind(_scroll));
    this.addEventListener('move', this.__bind(_positionScrollbar));

    if (this.autoSetScrollBounds) {
      fps.removeEventListener('tick', this.__bind(_update));
      fps.addEventListener('tick', this.__bind(_update));
    }

    this.setScrollBounds();
  };

  /**
   * unbinds events on Scrollable object
   *
   * @method _dispose
   * @memberof dom.Scrollable
   * @instance
   * @private
   **/
  var _dispose = function(){
    this.el.removeEventListener('mousewheel', this.__bind(_scroll));
    this.removeEventListener('move', this.__bind(_positionScrollbar));
    fps.removeEventListener('tick', this.__bind(_update));

    if (this.__removeScrollbarTimeout) clearTimeout(this.__removeScrollbarTimeout);
  };

  /**
   * update scroll bounds every 10 ticks
   *
   * @method _update
   * @memberof dom.Scrollable
   * @instance
   * @private
   **/
  var _update = function(){
    if (this.autoSetScrollBounds) {
      this.__scollTicks++;

      if (this.__scollTicks % this.__scrollTicksInterval === 0) {
        this.setScrollBounds();
        this.__scollTicks = 0;
        this.__scrollTicksInterval = Math.round(createjs.Ticker.getMeasuredFPS() * this.fraction.updateScrollBounds);
      }
    } else {
      fps.removeEventListener('tick', this.__bind(_update));
    }
  };

  /**
   * move position scrollbar
   *
   * @method _positionScrollbar
   * @memberof dom.Scrollable
   * @instance
   * @private
   **/
  var _positionScrollbar = function(){
    if (this.parent.vScrollbar) {
      var percentage = this.y / this.borders.y[0];
      this.parent.vScrollbar.el.style.top = (percentage * this.parent.vScrollbar.offset) + 'px';
      this.parent.vScrollbar.$el.addClass('active');

      if (this.__removeScrollbarTimeout) clearTimeout(this.__removeScrollbarTimeout);
      this.__removeScrollbarTimeout = setTimeout(this.__bind(_removeScrollbar), 300);
    }
  };

  var _removeScrollbar = function(){
    this.parent.vScrollbar.$el.removeClass('active');
  };

  /**
   * fake moving moveclip on
   *
   * @method _scroll
   * @memberof dom.Scrollable
   * @instance
   * @private
   **/
  var _scroll = function(event){
    var options = {};
    options.onUpdate = _positionScrollbar;
    options.onUpdateScope = this;
    options.x = Math.max(this.borders.x[0], Math.min(this.borders.x[1], this.x - event.deltaX));
    options.y = Math.max(this.borders.y[0], Math.min(this.borders.y[1], this.y - event.deltaY));
    options.ease = Quint.easeOut;

    TweenLite.to(this, 0.6, options);
  };

  /**
   * holds MoveClip bounds while animating via scrolling
   *
   * @method _hold
   * @memberof dom.Scrollable
   * @instance
   * @private
   **/
  var _hold = function(){
    this._hold('x');
    this._hold('y');
  };

  return Scrollable;
});
<commit_msg>adjust scrollbar<commit_after>/*
 * Scrollable.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
define([
  '../sys',
  '../utils/fps',
  './MoveClip',
  'EaselJS'
],
function(
  sys,
  fps,
  MoveClip,
  createjs
) {
  /**
   * Scrollable Container that Auto-sets borders based on parent container
   *
   * @class Scrollable
   * @memberof dom
   * @extends dom.MoveClip
   *
   * @param {Handlebars} template should be a compiled handlebars template. alternatively can be a plain string or jquery object
   * @param {object} data the data to pass through the template, optional
   * @param {object} options optional options to overwrite container properties before inheriting
   **/
  function Scrollable(template, data, options){
    // extend from base class
    MoveClip.call(this, template, data, options);

    this.borders.x = [0, 0];
    this.borders.y = [0, 0];

    this.fraction.release.x = 0.75;
    this.fraction.release.y = 0.75;

    this.fraction.updateScrollBounds = 1;

    this.__scollTicks = 0;
    this.__scrollTicksInterval = Math.round(createjs.Ticker.getMeasuredFPS() * this.fraction.updateScrollBounds);
    this.__scrollOldSize = {width: 0, height: 0};
    this.__scrollOldParentSize = {width: 0, height: 0};
    this.__unsetScrollbarTimeout = null;
    this.autoSetScrollBounds = true;

    this.on('addedToStage', _render, this);
    this.on('removedFromStage', _dispose, this);
  }

  var p = sys.extend(Scrollable, MoveClip);

  /**
   * sets bounds for moveclip based on scrollable parent
   *
   * @method _render
   * @memberof dom.Scrollable
   * @instance
   * @public
   **/
  p.setScrollBounds = function(){
    if (this.parent) {
      var parentSize = {width: this.parent.$el.outerWidth(), height: this.parent.$el.outerHeight()};
      var scrollSize = {width: this.$el.outerWidth(), height: this.$el.outerHeight()};

      if ((this.__scrollOldSize.width === scrollSize.width && this.__scrollOldSize.height === scrollSize.height) &&
        (this.__scrollOldParentSize.width === parentSize.width && this.__scrollOldParentSize.height === parentSize.height)) {
        return;
      }

      var offsetSize = {width: scrollSize.width - parentSize.width, height: scrollSize.height - parentSize.height};
      offsetSize.width = Math.max(0, offsetSize.width);
      offsetSize.height = Math.max(0, offsetSize.height);

      this.borders.x = [0 - offsetSize.width, 0];
      this.borders.y = [0 - offsetSize.height, 0];

      if (this.parent.vScrollbar) {
        var scrollbarHeightOffset = scrollSize.height / parentSize.height;
        scrollbarHeightOffset = Math.sqrt(scrollbarHeightOffset);

        var scrollbarHeight = Math.max(75, Math.min(parentSize.height * (1 / scrollbarHeightOffset)));

        var scrollbarMarginTop = parseFloat(this.parent.vScrollbar.$el.css('margin-top').replace('px', ''));
        if (isNaN(scrollbarMarginTop)) scrollbarMarginTop = 0;

        var scrollbarMarginBottom = parseFloat(this.parent.vScrollbar.$el.css('margin-bottom').replace('px', ''));
        if (isNaN(scrollbarMarginBottom)) scrollbarMarginBottom = 0;

        this.parent.vScrollbar.el.style.height = scrollbarHeight + 'px';
        this.parent.vScrollbar.offset = parentSize.height - scrollbarHeight - scrollbarMarginTop - scrollbarMarginBottom;
      }

      this.elastic.x = offsetSize.width > 0 ? 0.1 : 0;
      this.elastic.y = offsetSize.height > 0 ? 0.1 : 0;

      this.__scrollOldSize = scrollSize;
      this.__scrollOldParentSize = parentSize;
    }
  };

  /**
   * initializes events on Scrollable object
   *
   * @method _render
   * @memberof dom.Scrollable
   * @instance
   * @private
   **/
  var _render = function(){
    this.el.addEventListener('mousewheel', this.__bind(_scroll));
    this.addEventListener('move', this.__bind(_positionScrollbar));

    if (this.autoSetScrollBounds) {
      fps.removeEventListener('tick', this.__bind(_update));
      fps.addEventListener('tick', this.__bind(_update));
    }

    this.setScrollBounds();
  };

  /**
   * unbinds events on Scrollable object
   *
   * @method _dispose
   * @memberof dom.Scrollable
   * @instance
   * @private
   **/
  var _dispose = function(){
    this.el.removeEventListener('mousewheel', this.__bind(_scroll));
    this.removeEventListener('move', this.__bind(_positionScrollbar));
    fps.removeEventListener('tick', this.__bind(_update));

    if (this.__unsetScrollbarTimeout) clearTimeout(this.__unsetScrollbarTimeout);
  };

  /**
   * update scroll bounds every 10 ticks
   *
   * @method _update
   * @memberof dom.Scrollable
   * @instance
   * @private
   **/
  var _update = function(){
    if (this.autoSetScrollBounds) {
      this.__scollTicks++;

      if (this.__scollTicks % this.__scrollTicksInterval === 0) {
        this.setScrollBounds();
        this.__scollTicks = 0;
        this.__scrollTicksInterval = Math.round(createjs.Ticker.getMeasuredFPS() * this.fraction.updateScrollBounds);
      }
    } else {
      fps.removeEventListener('tick', this.__bind(_update));
    }
  };

  /**
   * move position scrollbar
   *
   * @method _positionScrollbar
   * @memberof dom.Scrollable
   * @instance
   * @private
   **/
  var _positionScrollbar = function(){
    if (this.parent.vScrollbar) {
      var percentage = this.y / this.borders.y[0];

      this.parent.vScrollbar.el.style.top = (percentage * this.parent.vScrollbar.offset) + 'px';
      this.parent.vScrollbar.$el.addClass('active');

      if (this.__unsetScrollbarTimeout) clearTimeout(this.__unsetScrollbarTimeout);
      this.__unsetScrollbarTimeout = setTimeout(this.__bind(_unsetScrollbar), 300);
    }
  };

  /**
   * unsets scrollbar visibility status
   *
   * @method _unsetScrollbar
   * @memberof dom.Scrollable
   * @instance
   * @private
   **/
  var _unsetScrollbar = function(){
    this.parent.vScrollbar.$el.removeClass('active');
  };

  /**
   * fake moving moveclip on
   *
   * @method _scroll
   * @memberof dom.Scrollable
   * @instance
   * @private
   **/
  var _scroll = function(event){
    var options = {};
    options.onUpdate = _positionScrollbar;
    options.onUpdateScope = this;
    options.x = Math.max(this.borders.x[0], Math.min(this.borders.x[1], this.x - event.deltaX));
    options.y = Math.max(this.borders.y[0], Math.min(this.borders.y[1], this.y - event.deltaY));
    options.ease = Quint.easeOut;

    TweenLite.to(this, 0.6, options);
  };

  /**
   * holds MoveClip bounds while animating via scrolling
   *
   * @method _hold
   * @memberof dom.Scrollable
   * @instance
   * @private
   **/
  var _hold = function(){
    this._hold('x');
    this._hold('y');
  };

  return Scrollable;
});
<|endoftext|>


<commit_before>"use strict";

var os = require("os");
var path = require("path");
var _ = require("lodash");

var api = require("./api");
var utils = require("./utils");

var EXPORTED_JSON_KEYS = [
  "localId",
  "email",
  "emailVerified",
  "passwordHash",
  "salt",
  "displayName",
  "photoUrl",
  "lastLoginAt",
  "createdAt",
  "phoneNumber",
];
var EXPORTED_JSON_KEYS_RENAMING = {
  lastLoginAt: "lastSignedInAt",
};
var EXPORTED_PROVIDER_USER_INFO_KEYS = ["providerId", "rawId", "email", "displayName", "photoUrl"];
var PROVIDER_ID_INDEX_MAP = {
  "google.com": 7,
  "facebook.com": 11,
  "twitter.com": 15,
  "github.com": 19,
};

var _escapeComma = function(str) {
  if (str.indexOf(",") !== -1) {
    // Encapsulate the string with quotes if it contains a comma.
    return `"${str}"`;
  }
  return str;
};

var _convertToNormalBase64 = function(data) {
  return data.replace(/_/g, "/").replace(/-/g, "+");
};

var _addProviderUserInfo = function(providerInfo, arr, startPos) {
  arr[startPos] = providerInfo.rawId;
  arr[startPos + 1] = providerInfo.email || "";
  arr[startPos + 2] = _escapeComma(providerInfo.displayName || "");
  arr[startPos + 3] = providerInfo.photoUrl || "";
};

var _transUserToArray = function(user) {
  var arr = Array(26).fill("");
  arr[0] = user.localId;
  arr[1] = user.email || "";
  arr[2] = user.emailVerified || false;
  arr[3] = _convertToNormalBase64(user.passwordHash || "");
  arr[4] = _convertToNormalBase64(user.salt || "");
  arr[5] = _escapeComma(user.displayName || "");
  arr[6] = user.photoUrl || "";
  for (var i = 0; i < (!user.providerUserInfo ? 0 : user.providerUserInfo.length); i++) {
    var providerInfo = user.providerUserInfo[i];
    if (providerInfo && PROVIDER_ID_INDEX_MAP[providerInfo.providerId]) {
      _addProviderUserInfo(providerInfo, arr, PROVIDER_ID_INDEX_MAP[providerInfo.providerId]);
    }
  }
  arr[23] = user.createdAt;
  arr[24] = user.lastLoginAt;
  arr[25] = user.phoneNumber;
  return arr;
};

var _transUserJson = function(user) {
  var newUser = {};
  _.each(_.pick(user, EXPORTED_JSON_KEYS), function(value, key) {
    var newKey = EXPORTED_JSON_KEYS_RENAMING[key] || key;
    newUser[newKey] = value;
  });
  if (newUser.passwordHash) {
    newUser.passwordHash = _convertToNormalBase64(newUser.passwordHash);
  }
  if (newUser.salt) {
    newUser.salt = _convertToNormalBase64(newUser.salt);
  }
  if (user.providerUserInfo) {
    newUser.providerUserInfo = [];
    user.providerUserInfo.forEach(function(providerInfo) {
      if (!_.includes(Object.keys(PROVIDER_ID_INDEX_MAP), providerInfo.providerId)) {
        return;
      }
      newUser.providerUserInfo.push(_.pick(providerInfo, EXPORTED_PROVIDER_USER_INFO_KEYS));
    });
  }
  return newUser;
};

var validateOptions = function(options, fileName) {
  var exportOptions = {};
  if (fileName === undefined) {
    return utils.reject("Must specify data file", { exit: 1 });
  }
  var extName = path.extname(fileName.toLowerCase());
  if (extName === ".csv") {
    exportOptions.format = "csv";
  } else if (extName === ".json") {
    exportOptions.format = "json";
  } else if (options.format) {
    var format = options.format.toLowerCase();
    if (format === "csv" || format === "json") {
      exportOptions.format = format;
    } else {
      return utils.reject("Unsupported data file format, should be csv or json", { exit: 1 });
    }
  } else {
    return utils.reject("Please specify data file format in file name, or use `format` parameter", {
      exit: 1,
    });
  }
  return exportOptions;
};

var _writeUsersToFile = (function() {
  var jsonSep = "";
  return function(userList, format, writeStream) {
    userList.map(function(user) {
      if (user.passwordHash && user.version !== 0) {
        // Password isn't hashed by default Scrypt.
        delete user.passwordHash;
        delete user.salt;
      }
      if (format === "csv") {
        writeStream.write(_transUserToArray(user).join(",") + "," + os.EOL, "utf8");
      } else {
        writeStream.write(jsonSep + JSON.stringify(_transUserJson(user), null, 2), "utf8");
        jsonSep = "," + os.EOL;
      }
    });
  };
})();

var serialExportUsers = function(projectId, options) {
  var postBody = {
    targetProjectId: projectId,
    maxResults: options.batchSize,
  };
  if (options.nextPageToken) {
    postBody.nextPageToken = options.nextPageToken;
  }
  return api
    .request("POST", "/identitytoolkit/v3/relyingparty/downloadAccount", {
      auth: true,
      json: true,
      data: postBody,
      origin: api.googleOrigin,
    })
    .then(function(ret) {
      var userList = ret.body.users;
      if (userList && userList.length > 0) {
        _writeUsersToFile(userList, options.format, options.writeStream);
        utils.logSuccess("Exported " + userList.length + " account(s) successfully.");
        options.nextPageToken = ret.body.nextPageToken;
        return serialExportUsers(projectId, options);
      }
    });
};

var accountExporter = {
  validateOptions: validateOptions,
  serialExportUsers: serialExportUsers,
};

module.exports = accountExporter;
<commit_msg>Don't iterate randomly on auth:export command (#1636)<commit_after>"use strict";

var os = require("os");
var path = require("path");
var _ = require("lodash");

var api = require("./api");
var utils = require("./utils");

var EXPORTED_JSON_KEYS = [
  "localId",
  "email",
  "emailVerified",
  "passwordHash",
  "salt",
  "displayName",
  "photoUrl",
  "lastLoginAt",
  "createdAt",
  "phoneNumber",
];
var EXPORTED_JSON_KEYS_RENAMING = {
  lastLoginAt: "lastSignedInAt",
};
var EXPORTED_PROVIDER_USER_INFO_KEYS = ["providerId", "rawId", "email", "displayName", "photoUrl"];
var PROVIDER_ID_INDEX_MAP = {
  "google.com": 7,
  "facebook.com": 11,
  "twitter.com": 15,
  "github.com": 19,
};

var _escapeComma = function(str) {
  if (str.indexOf(",") !== -1) {
    // Encapsulate the string with quotes if it contains a comma.
    return `"${str}"`;
  }
  return str;
};

var _convertToNormalBase64 = function(data) {
  return data.replace(/_/g, "/").replace(/-/g, "+");
};

var _addProviderUserInfo = function(providerInfo, arr, startPos) {
  arr[startPos] = providerInfo.rawId;
  arr[startPos + 1] = providerInfo.email || "";
  arr[startPos + 2] = _escapeComma(providerInfo.displayName || "");
  arr[startPos + 3] = providerInfo.photoUrl || "";
};

var _transUserToArray = function(user) {
  var arr = Array(26).fill("");
  arr[0] = user.localId;
  arr[1] = user.email || "";
  arr[2] = user.emailVerified || false;
  arr[3] = _convertToNormalBase64(user.passwordHash || "");
  arr[4] = _convertToNormalBase64(user.salt || "");
  arr[5] = _escapeComma(user.displayName || "");
  arr[6] = user.photoUrl || "";
  for (var i = 0; i < (!user.providerUserInfo ? 0 : user.providerUserInfo.length); i++) {
    var providerInfo = user.providerUserInfo[i];
    if (providerInfo && PROVIDER_ID_INDEX_MAP[providerInfo.providerId]) {
      _addProviderUserInfo(providerInfo, arr, PROVIDER_ID_INDEX_MAP[providerInfo.providerId]);
    }
  }
  arr[23] = user.createdAt;
  arr[24] = user.lastLoginAt;
  arr[25] = user.phoneNumber;
  return arr;
};

var _transUserJson = function(user) {
  var newUser = {};
  _.each(_.pick(user, EXPORTED_JSON_KEYS), function(value, key) {
    var newKey = EXPORTED_JSON_KEYS_RENAMING[key] || key;
    newUser[newKey] = value;
  });
  if (newUser.passwordHash) {
    newUser.passwordHash = _convertToNormalBase64(newUser.passwordHash);
  }
  if (newUser.salt) {
    newUser.salt = _convertToNormalBase64(newUser.salt);
  }
  if (user.providerUserInfo) {
    newUser.providerUserInfo = [];
    user.providerUserInfo.forEach(function(providerInfo) {
      if (!_.includes(Object.keys(PROVIDER_ID_INDEX_MAP), providerInfo.providerId)) {
        return;
      }
      newUser.providerUserInfo.push(_.pick(providerInfo, EXPORTED_PROVIDER_USER_INFO_KEYS));
    });
  }
  return newUser;
};

var validateOptions = function(options, fileName) {
  var exportOptions = {};
  if (fileName === undefined) {
    return utils.reject("Must specify data file", { exit: 1 });
  }
  var extName = path.extname(fileName.toLowerCase());
  if (extName === ".csv") {
    exportOptions.format = "csv";
  } else if (extName === ".json") {
    exportOptions.format = "json";
  } else if (options.format) {
    var format = options.format.toLowerCase();
    if (format === "csv" || format === "json") {
      exportOptions.format = format;
    } else {
      return utils.reject("Unsupported data file format, should be csv or json", { exit: 1 });
    }
  } else {
    return utils.reject("Please specify data file format in file name, or use `format` parameter", {
      exit: 1,
    });
  }
  return exportOptions;
};

var _writeUsersToFile = (function() {
  var jsonSep = "";
  return function(userList, format, writeStream) {
    userList.map(function(user) {
      if (user.passwordHash && user.version !== 0) {
        // Password isn't hashed by default Scrypt.
        delete user.passwordHash;
        delete user.salt;
      }
      if (format === "csv") {
        writeStream.write(_transUserToArray(user).join(",") + "," + os.EOL, "utf8");
      } else {
        writeStream.write(jsonSep + JSON.stringify(_transUserJson(user), null, 2), "utf8");
        jsonSep = "," + os.EOL;
      }
    });
  };
})();

var serialExportUsers = function(projectId, options) {
  var postBody = {
    targetProjectId: projectId,
    maxResults: options.batchSize,
  };
  if (options.nextPageToken) {
    postBody.nextPageToken = options.nextPageToken;
  }
  return api
    .request("POST", "/identitytoolkit/v3/relyingparty/downloadAccount", {
      auth: true,
      json: true,
      data: postBody,
      origin: api.googleOrigin,
    })
    .then(function(ret) {
      var userList = ret.body.users;
      if (userList && userList.length > 0) {
        _writeUsersToFile(userList, options.format, options.writeStream);
        utils.logSuccess("Exported " + userList.length + " account(s) successfully.");
        // The identitytoolkit API do not return a nextPageToken value
        // consistently when the last page is reached
        if (!ret.body.nextPageToken) {
          return;
        }
        options.nextPageToken = ret.body.nextPageToken;
        return serialExportUsers(projectId, options);
      }
    });
};

var accountExporter = {
  validateOptions: validateOptions,
  serialExportUsers: serialExportUsers,
};

module.exports = accountExporter;
<|endoftext|>


<commit_before>// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')

module.exports = {
  build: {
    env: require('./prod.env'),
    index: path.resolve(__dirname, '../dist/index.html'),
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: '',
    assetsPublicPath: '/',
    productionSourceMap: false,
    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  },
  dev: {
    env: require('./dev.env'),
    port: 8080,
    autoOpenBrowser: true,
    assetsSubDirectory: '',
    assetsPublicPath: '/',
    proxyTable: {
      '/api': "http://localhost:7623/",
      '/img': "http://localhost:7623/",
    },
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false
  }
}
<commit_msg>Fix lint errors<commit_after>// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')

module.exports = {
  build: {
    env: require('./prod.env'),
    index: path.resolve(__dirname, '../dist/index.html'),
    assetsRoot: path.resolve(__dirname, '../dist'),
    assetsSubDirectory: '',
    assetsPublicPath: '/',
    productionSourceMap: false,
    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    productionGzip: false,
    productionGzipExtensions: ['js', 'css'],
    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    bundleAnalyzerReport: process.env.npm_config_report
  },
  dev: {
    env: require('./dev.env'),
    port: 8080,
    autoOpenBrowser: true,
    assetsSubDirectory: '',
    assetsPublicPath: '/',
    proxyTable: {
      '/api': 'http://localhost:7623/',
      '/img': 'http://localhost:7623/',
    },
    // CSS Sourcemaps off by default because relative paths are "buggy"
    // with this option, according to the CSS-Loader README
    // (https://github.com/webpack/css-loader#sourcemaps)
    // In our experience, they generally work as expected,
    // just be aware of this issue when enabling this option.
    cssSourceMap: false
  }
}
<|endoftext|>


<commit_before>/*!
 * Amplify Store - Persistent Client-Side Storage @VERSION
 *
 * Copyright 2011 appendTo LLC. (http://appendto.com/team)
 * Dual licensed under the MIT or GPL licenses.
 * http://appendto.com/open-source-licenses
 *
 * http://amplifyjs.com
 */
(function( amplify, undefined ) {

var store = amplify.store = function( key, value, options, type ) {
	var type = store.type;
	if ( options && options.type && options.type in store.types ) {
		type = options.type;
	}
	return store.types[ type ]( key, value, options || {} );
};

store.types = {};
store.type = null;
store.addType = function( type, storage ) {
	if ( !store.type ) {
		store.type = type;
	}

	store.types[ type ] = storage;
	store[ type ] = function( key, value, options ) {
		options = options || {};
		options.type = type;
		return store( key, value, options );
	};
}

function createSimpleStorage( storageType, storage ) {
	var values = storage.__amplify__ ? JSON.parse( storage.__amplify__ ) : {};
	store.addType( storageType, function( key, value, options ) {
		var ret = value,
			now = (new Date()).getTime(),
			storedValue,
			parsed;

		if ( !key ) {
			ret = {};
			for ( key in values ) {
				storedValue = storage[ key ];
				parsed = storedValue ? JSON.parse( storedValue ) : { expires: -1 };
				if ( parsed.expires && parsed.expires <= now ) {
					delete storage[ key ];
					delete values[ key ];
				} else {
					ret[ key.replace( /^__amplify__/, "" ) ] = parsed.data;
				}
			}
			storage.__amplify__ = JSON.stringify( values );
			return ret;
		}

		// protect against overwriting built-in properties
		key = "__amplify__" + key;

		if ( value === undefined ) {
			if ( values[ key ] ) {
				storedValue = storage[ key ];
				parsed = storedValue ? JSON.parse( storedValue ) : { expires: -1 };
				if ( parsed.expires && parsed.expires <= now ) {
					delete storage[ key ];
					delete values[ key ];
				} else {
					return parsed.data;
				}
			}
		} else {
			if ( value === null ) {
				delete storage[ key ];
				delete values[ key ];
			} else {
				storage[ key ] = JSON.stringify({
					data: value,
					expires: options.expires ? now + options.expires : null
				});
				values[ key ] = true;
			}
		}

		storage.__amplify__ = JSON.stringify( values );
		return ret;
	});
}

// localStorage + sessionStorage
// IE 8+, Firefox 3.5+, Safari 4+, Chrome 4+, Opera 10.5+, iPhone 2+, Android 2+
for ( var webStorageType in { localStorage: 1, sessionStorage: 1 } ) {
	// try/catch for file protocol in Firefox
	try {
		if ( window[ webStorageType ].getItem ) {
			createSimpleStorage( webStorageType, window[ webStorageType ] );
		}
	} catch( e ) {}
}

// globalStorage
// non-standard: Firefox 2+
// https://developer.mozilla.org/en/dom/storage#globalStorage
if ( window.globalStorage ) {
	createSimpleStorage( "globalStorage",
		window.globalStorage[ window.location.hostname ] );
}

// userData
// non-standard: IE 5+
// http://msdn.microsoft.com/en-us/library/ms531424(v=vs.85).aspx
(function() {
	// append to html instead of body so we can do this from the head
	var div = document.createElement( "div" ),
		attrKey = "amplify",
		attrs;
	div.style.display = "none";
	document.documentElement.appendChild( div );
	if ( div.addBehavior ) {
		div.addBehavior( "#default#userdata" );
		div.load( attrKey );
		attrs = div.getAttribute( attrKey ) ? JSON.parse( div.getAttribute( attrKey ) ) : {};

		store.addType( "userData", function( key, value, options ) {
			var ret = value,
				now = (new Date()).getTime(),
				attr,
				parsed;

			if ( !key ) {
				ret = {};
				for ( key in attrs ) {
					attr = div.getAttribute( key );
					parsed = attr ? JSON.parse( attr ) : { expires: -1 };
					if ( parsed.expires && parsed.expires <= now ) {
						div.removeAttribute( key );
						delete attrs[ key ];
					} else {
						ret[ key ] = parsed.data;
					}
				}
				div.setAttribute( attrKey, JSON.stringify( attrs ) );
				div.save( attrKey );
				return ret;
			}

			// convert invalid characters to dashes
			// http://www.w3.org/TR/REC-xml/#NT-Name
			// simplified to assume the starting character is valid
			// also removed colon as it is invalid in HTML attribute names
			key = key.replace( /[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u37f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/g, "-" );

			if ( value === undefined ) {
				if ( key in attrs ) {
					attr = div.getAttribute( key );
					parsed = attr ? JSON.parse( attr ) : { expires: -1 };
					if ( parsed.expires && parsed.expires <= now ) {
						div.removeAttribute( key );
						delete attrs[ key ];
					} else {
						return parsed.data;
					}
				}
			} else {
				if ( value === null ) {
					div.removeAttribute( key );
					delete attrs[ key ];
				} else {
					div.setAttribute( key, JSON.stringify({
						data: value,
						expires: (options.expires ? (now + options.expires) : null)
					}) );
					attrs[ key ] = true;
				}
			}

			div.setAttribute( attrKey, JSON.stringify( attrs ) );
			div.save( attrKey );
			return ret;
		});
	}
}() );

// in-memory storage
// fallback for all browsers to enable the API even if we can't persist data
createSimpleStorage( "memory", {} );

}( this.amplify = this.amplify || {} ) );
<commit_msg>Store: Added consistent error handling for exceeding the storage quota.<commit_after>/*!
 * Amplify Store - Persistent Client-Side Storage @VERSION
 *
 * Copyright 2011 appendTo LLC. (http://appendto.com/team)
 * Dual licensed under the MIT or GPL licenses.
 * http://appendto.com/open-source-licenses
 *
 * http://amplifyjs.com
 */
(function( amplify, undefined ) {

var store = amplify.store = function( key, value, options, type ) {
	var type = store.type;
	if ( options && options.type && options.type in store.types ) {
		type = options.type;
	}
	return store.types[ type ]( key, value, options || {} );
};

store.types = {};
store.type = null;
store.addType = function( type, storage ) {
	if ( !store.type ) {
		store.type = type;
	}

	store.types[ type ] = storage;
	store[ type ] = function( key, value, options ) {
		options = options || {};
		options.type = type;
		return store( key, value, options );
	};
}
store.error = function() {
	return "amplify.store quota exceeded";
};

function createSimpleStorage( storageType, storage ) {
	var values = storage.__amplify__ ? JSON.parse( storage.__amplify__ ) : {};
	store.addType( storageType, function( key, value, options ) {
		var ret = value,
			now = (new Date()).getTime(),
			storedValue,
			parsed;

		if ( !key ) {
			ret = {};
			for ( key in values ) {
				storedValue = storage[ key ];
				parsed = storedValue ? JSON.parse( storedValue ) : { expires: -1 };
				if ( parsed.expires && parsed.expires <= now ) {
					delete storage[ key ];
					delete values[ key ];
				} else {
					ret[ key.replace( /^__amplify__/, "" ) ] = parsed.data;
				}
			}
			storage.__amplify__ = JSON.stringify( values );
			return ret;
		}

		// protect against overwriting built-in properties
		key = "__amplify__" + key;

		if ( value === undefined ) {
			if ( values[ key ] ) {
				storedValue = storage[ key ];
				parsed = storedValue ? JSON.parse( storedValue ) : { expires: -1 };
				if ( parsed.expires && parsed.expires <= now ) {
					delete storage[ key ];
					delete values[ key ];
				} else {
					return parsed.data;
				}
			}
		} else {
			if ( value === null ) {
				delete storage[ key ];
				delete values[ key ];
			} else {
				parsed = JSON.stringify({
					data: value,
					expires: options.expires ? now + options.expires : null
				});
				try {
					storage[ key ] = parsed;
					values[ key ] = true;
				} catch( error ) {
					throw store.error();
				}
			}
		}

		storage.__amplify__ = JSON.stringify( values );
		return ret;
	});
}

// localStorage + sessionStorage
// IE 8+, Firefox 3.5+, Safari 4+, Chrome 4+, Opera 10.5+, iPhone 2+, Android 2+
for ( var webStorageType in { localStorage: 1, sessionStorage: 1 } ) {
	// try/catch for file protocol in Firefox
	try {
		if ( window[ webStorageType ].getItem ) {
			createSimpleStorage( webStorageType, window[ webStorageType ] );
		}
	} catch( e ) {}
}

// globalStorage
// non-standard: Firefox 2+
// https://developer.mozilla.org/en/dom/storage#globalStorage
if ( window.globalStorage ) {
	createSimpleStorage( "globalStorage",
		window.globalStorage[ window.location.hostname ] );
}

// userData
// non-standard: IE 5+
// http://msdn.microsoft.com/en-us/library/ms531424(v=vs.85).aspx
(function() {
	// append to html instead of body so we can do this from the head
	var div = document.createElement( "div" ),
		attrKey = "amplify",
		attrs;
	div.style.display = "none";
	document.documentElement.appendChild( div );
	if ( div.addBehavior ) {
		div.addBehavior( "#default#userdata" );
		div.load( attrKey );
		attrs = div.getAttribute( attrKey ) ? JSON.parse( div.getAttribute( attrKey ) ) : {};

		store.addType( "userData", function( key, value, options ) {
			var ret = value,
				now = (new Date()).getTime(),
				attr,
				parsed,
				prevValue;

			if ( !key ) {
				ret = {};
				for ( key in attrs ) {
					attr = div.getAttribute( key );
					parsed = attr ? JSON.parse( attr ) : { expires: -1 };
					if ( parsed.expires && parsed.expires <= now ) {
						div.removeAttribute( key );
						delete attrs[ key ];
					} else {
						ret[ key ] = parsed.data;
					}
				}
				div.setAttribute( attrKey, JSON.stringify( attrs ) );
				div.save( attrKey );
				return ret;
			}

			// convert invalid characters to dashes
			// http://www.w3.org/TR/REC-xml/#NT-Name
			// simplified to assume the starting character is valid
			// also removed colon as it is invalid in HTML attribute names
			key = key.replace( /[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u37f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/g, "-" );

			if ( value === undefined ) {
				if ( key in attrs ) {
					attr = div.getAttribute( key );
					parsed = attr ? JSON.parse( attr ) : { expires: -1 };
					if ( parsed.expires && parsed.expires <= now ) {
						div.removeAttribute( key );
						delete attrs[ key ];
					} else {
						return parsed.data;
					}
				}
			} else {
				if ( value === null ) {
					div.removeAttribute( key );
					delete attrs[ key ];
				} else {
					// we need to get the previous value in case we need to rollback
					prevValue = div.getAttribute( key );
					div.setAttribute( key, JSON.stringify({
						data: value,
						expires: (options.expires ? (now + options.expires) : null)
					}) );
					attrs[ key ] = true;
				}
			}

			div.setAttribute( attrKey, JSON.stringify( attrs ) );
			try {
				div.save( attrKey );
			} catch ( error ) {
				// roll the value back to the previous value
				// this will avoid constantly hitting the quota
				if ( prevValue === undefined ) {
					div.removeAttribute( key );
					delete attrs[ key ];
				} else {
					div.setAttribute( key, prevValue );
				}
				throw store.error();
			}
			return ret;
		});
	}
}() );

// in-memory storage
// fallback for all browsers to enable the API even if we can't persist data
createSimpleStorage( "memory", {} );

}( this.amplify = this.amplify || {} ) );
<|endoftext|>


<commit_before>var Stremio = require("stremio-addons");
var http = require("http");
var _ = require("lodash");
var async = require("async");
var sift = require("sift");

var cfg = require("../lib/cfg");
var db = require("../lib/db");

function validate(args) {
    var meta = args.query;
    if (! (args.query || args.infoHash)) return { code: 0, message: "query/infoHash required" };
    if (meta && !meta.imdb_id) return { code: 1, message: "imdb_id required" };
    if (meta && (meta.type == "series" && !(meta.hasOwnProperty("episode") && meta.hasOwnProperty("season"))))
        return { code: 2, message: "season and episode required for series type" };
    return false;
};

function query(args, callback) {
    var start = Date.now();

    (function(next) {
        if (args.infoHash) return db.get(args.infoHash, function(err, res) { next(err, res && res[0] && res[0].value) });
        if (! args.query) return callback(new Error("must specify query or infoHash"));

        //var preferred = _.uniq(PREFERRED.concat(args.preferred || []), function(x) { return x.tag });
        var preferred = args.preferred || [];
        var prio = function(resolution) {
            return preferred.map(function(pref) {
                return db.getAvailForTorrent(resolution.torrent) >= pref.min_avail && resolution.file.tag.indexOf(pref.tag)!=-1
            }).reduce(function(a,b) { return a+b }, 0);
        };

        var resolution = null;
        var matches = db.lookup(args.query, 3);
        async.whilst(
            function() { return matches.length && (!resolution || prio(resolution) < preferred.length) },
            function(callback) {
                var hash = matches.shift().id;
                db.get(hash, function(err, res) {
                    if (err) return callback({ err: err });

                    var tor = res[0] && res[0].value;
                    if (! tor) return callback({ err: "hash not found "+hash });

                    var file = _.find(tor.files, function(f) {
                        return f.imdb_id == args.query.imdb_id &&
                            (args.query.season ? (f.season == args.query.season) : true) &&
                            (args.query.episode ? ((f.episode || []).indexOf(args.query.episode) != -1) : true)
                    });

                    if (db.isFileBlacklisted(file)) return callback(); // blacklisted tag

                    var res = { torrent: tor, file: file };
                    if (!resolution || prio(res) > prio(resolution)) resolution = res;

                    callback();
                });
            },
            function() { resolution ? next(resolution.err, resolution.torrent, resolution.file) : next() }
        );
    })(function(err, torrent, file) {
        // Output according to Stremio Addon API for stream.get
        // http://strem.io/addons-api
        callback(err, torrent ? _.extend({
            infoHash: torrent.infoHash.toLowerCase(),
            uploaders: db.getMaxPopularity(torrent), // optional
            downloaders: Math.max.apply(Math, _.values(torrent.popularity).map(function(x) { return x[1] }).concat(0)), // optional
            //map: torrent.files,
            //pieceLength: torrent.pieceLength,
            availability: db.getAvailForTorrent(torrent),
            sources: db.getSourcesForTorrent(torrent), // optional but preferred
            runtime: Date.now()-start // optional
        }, file ? {
            mapIdx: file.idx,
            tag: file.tag, filename: file.name,
        } : { }) : null);
    });
};

var service = new Stremio.Server({
    "stream.get": function(args, callback, user) {
        var error = validate(args);
        if (error) return callback(error);
        query(args, callback);
    },
    "stream.find": function(args, callback, user) {
        if ( args.items && Array.isArray(args.items)) {
            // OLD FORMAT; TODO: OBSOLETE
            var error = null;
            args.items.forEach(function(x) { error = error || validate(x) });
            if (error) return callback(error);

            async.map(args.items, query, function(err, items) {
                callback(err, items ? { items: items.map(function(x) {
                    return x ? { availability: x.availability, tag: x.tag } : null // TODO: send back number of candidates under _candidates
                }) } : null);
            });
        } else if (args.query) {
            // New format ; same as stream.get, even returns the full result; no point to slim it down, takes same time
            var error = validate(args);
            if (error) return callback(error);
            async.map([ _.extend({ preferred: [{ tag: "hd", min_avail: 2 }] }, args), args ], query, function(err, res) {
                callback(err, res ? _.chain(res).filter(function(x) { return x }).uniq(function(x) { return x.infoHash }).value() : undefined);
            });
        } else return callback({code: 10, message: "unsupported arguments"});
    },
    "stream.popularities": function(args, callback, user) {
        var popularities = { };
        db.indexes.meta.executeOnEveryNode(function(n) {
            popularities[n.key.split(" ")[0]] = Math.max.apply(null, n.data.map(function(k) { return db.indexes.seeders.get(k) })) || 0;
        });
        callback(null, { popularities: popularities });
    },
    "stats.get": function(args, callback, user) { // TODO
        var c = db.indexes.seeders.size;
        var items = 0, episodes = 0, movies = 0;
        db.indexes.meta.executeOnEveryNode(function(n) {
            if (n.key.indexOf(" ") != -1) episodes++; else movies++;
            items++;
        });
        callback(null, { statsNum: items+" movies and episodes", stats: [
            { name: "number of items - "+items, count: items, colour: items > 100 ? "green" : (items > 50 ? "yellow" : "red") },
            { name: "number of movies - "+movies, count: movies, colour: movies > 100 ? "green" : (movies > 50 ? "yellow" : "red") },
            { name: "number of episodes - "+episodes, count: episodes, colour: episodes > 100 ? "green" : (episodes > 50 ? "yellow" : "red") },
            { name: "number of torrents - "+c, count: c, colour: c > 1000 ? "green" : (c > 500 ? "yellow" : "red") }
        ] });
    },
}, { allow: [cfg.stremioCentral], secret: cfg.stremioSecret }, _.extend(require("./stremio-manifest"), _.pick(require("../package"), "version")));

function listen(port, ip) {
    var server = http.createServer(function (req, res) {
        service.middleware(req, res, function() { res.end() });
    }).on("listening", function()
    {
        console.log("Multipass Stremio Addon listening on "+server.address().port);
    });
    return server.listen(port, ip);
}

if (module.parent) { module.exports = listen; module.exports.service = service; }
else listen(process.env.PORT || 7000);
<commit_msg>Add-on: prepare for intercepting meta.find from cinemeta<commit_after>var Stremio = require("stremio-addons");
var http = require("http");
var _ = require("lodash");
var async = require("async");
var sift = require("sift");

var cfg = require("../lib/cfg");
var db = require("../lib/db");

function validate(args) {
    var meta = args.query;
    if (! (args.query || args.infoHash)) return { code: 0, message: "query/infoHash required" };
    if (meta && !meta.imdb_id) return { code: 1, message: "imdb_id required" };
    if (meta && (meta.type == "series" && !(meta.hasOwnProperty("episode") && meta.hasOwnProperty("season"))))
        return { code: 2, message: "season and episode required for series type" };
    return false;
};

function query(args, callback) {
    var start = Date.now();

    (function(next) {
        if (args.infoHash) return db.get(args.infoHash, function(err, res) { next(err, res && res[0] && res[0].value) });
        if (! args.query) return callback(new Error("must specify query or infoHash"));

        //var preferred = _.uniq(PREFERRED.concat(args.preferred || []), function(x) { return x.tag });
        var preferred = args.preferred || [];
        var prio = function(resolution) {
            return preferred.map(function(pref) {
                return db.getAvailForTorrent(resolution.torrent) >= pref.min_avail && resolution.file.tag.indexOf(pref.tag)!=-1
            }).reduce(function(a,b) { return a+b }, 0);
        };

        var resolution = null;
        var matches = db.lookup(args.query, 3);
        async.whilst(
            function() { return matches.length && (!resolution || prio(resolution) < preferred.length) },
            function(callback) {
                var hash = matches.shift().id;
                db.get(hash, function(err, res) {
                    if (err) return callback({ err: err });

                    var tor = res[0] && res[0].value;
                    if (! tor) return callback({ err: "hash not found "+hash });

                    var file = _.find(tor.files, function(f) {
                        return f.imdb_id == args.query.imdb_id &&
                            (args.query.season ? (f.season == args.query.season) : true) &&
                            (args.query.episode ? ((f.episode || []).indexOf(args.query.episode) != -1) : true)
                    });

                    if (db.isFileBlacklisted(file)) return callback(); // blacklisted tag

                    var res = { torrent: tor, file: file };
                    if (!resolution || prio(res) > prio(resolution)) resolution = res;

                    callback();
                });
            },
            function() { resolution ? next(resolution.err, resolution.torrent, resolution.file) : next() }
        );
    })(function(err, torrent, file) {
        // Output according to Stremio Addon API for stream.get
        // http://strem.io/addons-api
        callback(err, torrent ? _.extend({
            infoHash: torrent.infoHash.toLowerCase(),
            uploaders: db.getMaxPopularity(torrent), // optional
            downloaders: Math.max.apply(Math, _.values(torrent.popularity).map(function(x) { return x[1] }).concat(0)), // optional
            //map: torrent.files,
            //pieceLength: torrent.pieceLength,
            availability: db.getAvailForTorrent(torrent),
            sources: db.getSourcesForTorrent(torrent), // optional but preferred
            runtime: Date.now()-start // optional
        }, file ? {
            mapIdx: file.idx,
            tag: file.tag, filename: file.name,
        } : { }) : null);
    });
};

// var LID =
var manifest = _.merge({
    // this should be always overridable by stremio-manifest
    // stremio_LID: LID,
    // filter: _.object(["sort."+LID,"query."+LID],[{"$exists": true},{"$exists": true}])
    // set filter so that we intercept meta.find from cinemeta
}, require("./stremio-manifest"), _.pick(require("../package"), "version"));

var service = new Stremio.Server({
    "stream.get": function(args, callback, user) {
        var error = validate(args);
        if (error) return callback(error);
        query(args, callback);
    },
    "stream.find": function(args, callback, user) {
        if ( args.items && Array.isArray(args.items)) {
            // OLD FORMAT; TODO: OBSOLETE
            var error = null;
            args.items.forEach(function(x) { error = error || validate(x) });
            if (error) return callback(error);

            async.map(args.items, query, function(err, items) {
                callback(err, items ? { items: items.map(function(x) {
                    return x ? { availability: x.availability, tag: x.tag } : null // TODO: send back number of candidates under _candidates
                }) } : null);
            });
        } else if (args.query) {
            // New format ; same as stream.get, even returns the full result; no point to slim it down, takes same time
            var error = validate(args);
            if (error) return callback(error);
            async.map([ _.extend({ preferred: [{ tag: "hd", min_avail: 2 }] }, args), args ], query, function(err, res) {
                callback(err, res ? _.chain(res).filter(function(x) { return x }).uniq(function(x) { return x.infoHash }).value() : undefined);
            });
        } else return callback({code: 10, message: "unsupported arguments"});
    },
    "stream.popularities": function(args, callback, user) {
        var popularities = { };
        db.indexes.meta.executeOnEveryNode(function(n) {
            popularities[n.key.split(" ")[0]] = Math.max.apply(null, n.data.map(function(k) { return db.indexes.seeders.get(k) })) || 0;
        });
        callback(null, { popularities: popularities });
    },
    "stats.get": function(args, callback, user) { // TODO
        var c = db.indexes.seeders.size;
        var items = 0, episodes = 0, movies = 0;
        db.indexes.meta.executeOnEveryNode(function(n) {
            if (n.key.indexOf(" ") != -1) episodes++; else movies++;
            items++;
        });
        callback(null, { statsNum: items+" movies and episodes", stats: [
            { name: "number of items - "+items, count: items, colour: items > 100 ? "green" : (items > 50 ? "yellow" : "red") },
            { name: "number of movies - "+movies, count: movies, colour: movies > 100 ? "green" : (movies > 50 ? "yellow" : "red") },
            { name: "number of episodes - "+episodes, count: episodes, colour: episodes > 100 ? "green" : (episodes > 50 ? "yellow" : "red") },
            { name: "number of torrents - "+c, count: c, colour: c > 1000 ? "green" : (c > 500 ? "yellow" : "red") }
        ] });
    },
}, { allow: [cfg.stremioCentral], secret: cfg.stremioSecret }, manifest);

function listen(port, ip) {
    var server = http.createServer(function (req, res) {
        service.middleware(req, res, function() { res.end() });
    }).on("listening", function()
    {
        console.log("Multipass Stremio Addon listening on "+server.address().port);
    });
    return server.listen(port, ip);
}

if (module.parent) { module.exports = listen; module.exports.service = service; }
else listen(process.env.PORT || 7000);
<|endoftext|>


<commit_before>/*global templatePath */
var fs = require('fs-extra');

module.exports = {
  initSource: templatePath + 'init/',
  initDest: './',

  // Public: Copy the boilerplate in the templates init folder to
  // the user's root project folder. If no overrides are passed it
  // is copied from `../templates/default/init/*` The project name
  // passed in is used for removing original boilerplate.
  //
  // Examples:
  // require('./init').init('myProjectName');
  //
  // projectName - the {String} name of the user's meteor project
  //
  init: function(projectName, opts) {
    this.opts = opts;
    this.projectName = projectName;

    this._copyTemplate();
    this._removeOriginalMeteorFiles(projectName);

    console.log('\nPlease run mrt to install Iron Router\n');
  },

  // Private: Copy the boilerplate from templates directory and place it
  // in the user's project root directory.
  //
  _copyTemplate: function() {
    fs.copySync(this.initSource, this.initDest);

    fs.removeSync('./both/.gitkeep');
    fs.removeSync('./client/.gitkeep');
    fs.removeSync('./server/.gitkeep');

    console.log('    Created: .jshintrc');
    console.log('    Created: .jshintignore');
    console.log('    Created: smart.json');
    console.log('    Created: makefile');
  },

  // Private: Removes the original foo.html, foo.js, and foo.css files
  // that Meteor creates after a `meteor create foo` command
  //
  // projectName - The {String} name of the project (same as meteor create)
  //
  _removeOriginalMeteorFiles: function(projectName) {
    fs.removeSync('./'+ this.projectName +'.js');
    fs.removeSync('./'+ this.projectName +'.html');
    fs.removeSync('./'+ this.projectName +'.css');

    console.log('    Removed: original boilerplate');
  }
};

<commit_msg>call create router from init modules<commit_after>/*global templatePath */
var fs = require('fs-extra'),
    router = require('./router.js');

module.exports = {
  initSource: templatePath + 'init/',
  initDest: './',

  // Public: Copy the boilerplate in the templates init folder to
  // the user's root project folder. Creates a router and client
  // folder. The project name is used for removing original boilerplate.
  //
  // Examples:
  // require('./init').init('myProjectName');
  //
  // projectName - the {String} name of the user's meteor project
  //
  init: function(projectName, opts) {
    this.opts = opts;
    this.projectName = projectName;

    router.create();
    this._copyTemplate();
    this._removeOriginalMeteorFiles(projectName);

    console.log('\nPlease run mrt to install Iron Router\n');
  },

  // Private: Copy the boilerplate from templates directory and place it
  // in the user's project root directory.
  //
  _copyTemplate: function() {
    fs.copySync(this.initSource, this.initDest);

    fs.removeSync('./both/.gitkeep');
    fs.removeSync('./server/.gitkeep');

    console.log('    Created: .jshintrc');
    console.log('    Created: .jshintignore');
    console.log('    Created: smart.json');
    console.log('    Created: makefile');
  },

  // Private: Removes the original foo.html, foo.js, and foo.css files
  // that Meteor creates after a `meteor create foo` command
  //
  // projectName - The {String} name of the project (same as meteor create)
  //
  _removeOriginalMeteorFiles: function(projectName) {
    fs.removeSync('./'+ this.projectName +'.js');
    fs.removeSync('./'+ this.projectName +'.html');
    fs.removeSync('./'+ this.projectName +'.css');

    console.log('    Removed: original boilerplate');
  }
};

<|endoftext|>


<commit_before>function TiledCanvas (canvas, settings) {
    this.canvas = canvas;
    this.ctx = this.canvas.getContext('2d');
    this.leftTopX = 0;
    this.leftTopY = 0;
    this.affecting = [[0, 0], [0, 0]];
    this.chunks = {};
    this.settings = this.normalizeDefaults(settings, this.defaultSettings);
    this.contextQueue = [];
    this.context = this.createContext();
}

TiledCanvas.prototype.defaultSettings = {
    chunkSize: 256
};

TiledCanvas.prototype.cloneObject = function (obj) {
	var clone = {};
	for (var k in obj) {
		if (typeof obj[k] === "object" && !(obj[k] instanceof Array)) {
			clone[k] = this.cloneObject(obj[k]);
		} else {
			clone[k] = obj[k]
		}
	}
	return clone;
};

TiledCanvas.prototype.normalizeDefaults = function normalizeDefaults (target, defaults) {
	target = target || {};
	var normalized = this.cloneObject(target);
	for (var k in defaults) {
		if (typeof defaults[k] === "object" && !(defaults[k] instanceof Array)) {
			normalized[k] = this.normalizeDefaults(target[k] || {}, defaults[k]);
		} else {
			normalized[k] = target[k] || defaults[k];
		}
	}
	return normalized;
};

TiledCanvas.prototype.redraw = function redraw () {
    this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
    var startChunkX = Math.floor(this.leftTopX / this.settings.chunkSize),
        endChunkX =  Math.ceil((this.leftTopX + this.canvas.width) / this.settings.chunkSize),
        startChunkY = Math.floor(this.leftTopY / this.settings.chunkSize),
        endChunkY = Math.ceil((this.leftTopY + this.canvas.height) / this.settings.chunkSize);
    for (var chunkX = startChunkX; chunkX < endChunkX; chunkX++) {
        for (var chunkY = startChunkY; chunkY < endChunkY; chunkY++) {
            this.drawChunk(chunkX, chunkY);
        }
    }
};

TiledCanvas.prototype.drawChunk = function drawChunk (chunkX, chunkY) {
    if (this.chunks[chunkX] && this.chunks[chunkX][chunkY]) {
        this.ctx.drawImage(this.chunks[chunkX][chunkY].canvas, chunkX * this.settings.chunkSize - this.leftTopX, chunkY * this.settings.chunkSize - this.leftTopY);
    }
};

TiledCanvas.prototype.goto = function goto (x, y) {
    this.leftTopX = x;
    this.leftTopY = y;
    this.redraw();
};

TiledCanvas.prototype.execute = function execute () {
    this.executeNoRedraw();
    this.redraw();
};

TiledCanvas.prototype.executeNoRedraw = function executeNoRedraw () {
    for (var chunkX = this.affecting[0][0]; chunkX < this.affecting[1][0]; chunkX++) {
        for (var chunkY = this.affecting[0][1]; chunkY < this.affecting[1][1]; chunkY++) {
            this.executeChunk(chunkX, chunkY);
        }
    }
    this.contextQueue = [];
};

TiledCanvas.prototype.clearAll = function clearAll () {
    this.contextQueue = [];
    for (var chunkX in this.chunks) {
        for (var chunkY in this.chunks[chunkY]) {
            this.chunks[chunkX][chunkY].clearRect(this.chunks[chunkX][chunkY].canvas.width, this.chunks[chunkX][chunkY].canvas.height);
        }
    }
};

TiledCanvas.prototype.executeChunk = function executeChunk (chunkX, chunkY) {
    this.chunks[chunkX] = this.chunks[chunkX] || [];

    this.chunks[chunkX][chunkY] = this.chunks[chunkX][chunkY] || this.newCtx(this.settings.chunkSize, this.settings.chunkSize, -chunkX * this.settings.chunkSize, -chunkY * this.settings.chunkSize);
    var ctx = this.chunks[chunkX][chunkY];

    for (var queuekey = 0; queuekey < this.contextQueue.length; queuekey++) {
        if (typeof ctx[this.contextQueue[queuekey][0]] === 'function') {
            this.executeQueueOnChunk(ctx, this.contextQueue[queuekey]);
        } else {
            ctx[this.contextQueue[queuekey][0]] = this.contextQueue[queuekey][1];
        }
    }
};

TiledCanvas.prototype.executeQueueOnChunk = function executeQueueOnChunk (ctx, args) {
    ctx[args[0]].apply(ctx, Array.prototype.slice.call(args, 1));
};

TiledCanvas.prototype.cleanup = function cleanup (chunkX, chunkY, arguments) {
    if (typeof this.cleanupFunctions[arguments[0]] === 'function') {
        return this.cleanupFunctions[arguments[0]](arguments.slice(), chunkX * this.settings.chunkSize, chunkY * this.settings.chunkSize);
    }
    return arguments;
};

TiledCanvas.prototype.drawingRegion = function (startX, startY, endX, endY, border) {
    border = border || 0;
    this.affecting[0][0] = Math.floor((Math.min(startX, endX) - border) / this.settings.chunkSize);
    this.affecting[0][1] = Math.floor((Math.min(startY, endY) - border) / this.settings.chunkSize);
    this.affecting[1][0] = Math.ceil((Math.max(endX, startX) + border) / this.settings.chunkSize);
    this.affecting[1][1] = Math.ceil((Math.max(endY, startY) + border) / this.settings.chunkSize);
};

TiledCanvas.prototype.newCtx = function newCtx (width, height, translateX, translateY) {
    var ctx = document.createElement('canvas').getContext('2d');
    ctx.canvas.width = width;
    ctx.canvas.height = height;
    ctx.translate(translateX, translateY);
    return ctx;
};

TiledCanvas.prototype.createContext = function createContext () {
    var context = {};
    var ctx = document.createElement('canvas').getContext('2d');
    for (var key in ctx) {
        if (typeof ctx[key] === 'function') {
            context[key] = function (func) {
                this.contextQueue.push(arguments);
            }.bind(this, key);
        } else if (typeof ctx[key] !== 'object') {
            context.__defineGetter__(key, function (key) {
                var ctx = this.newCtx();
                for (var queuekey = 0; queuekey < this.contextQueue.length; queuekey++) {
                    if (typeof ctx[args[0]] === 'function') {
                        ctx[args[0]].apply(ctx, args.slice(1));
                    } else {
                        ctx[args[0]] = args[1];
                    }
                }
                return ctx[key];
            }.bind(this, key));

            context.__defineSetter__(key, function (key, value) {
                this.contextQueue.push(arguments);
            }.bind(this, key));
        }
    }
    return context;
};
<commit_msg>Fixed clearAll method<commit_after>function TiledCanvas (canvas, settings) {
    this.canvas = canvas;
    this.ctx = this.canvas.getContext('2d');
    this.leftTopX = 0;
    this.leftTopY = 0;
    this.affecting = [[0, 0], [0, 0]];
    this.chunks = {};
    this.settings = this.normalizeDefaults(settings, this.defaultSettings);
    this.contextQueue = [];
    this.context = this.createContext();
}

TiledCanvas.prototype.defaultSettings = {
    chunkSize: 256
};

TiledCanvas.prototype.cloneObject = function (obj) {
	var clone = {};
	for (var k in obj) {
		if (typeof obj[k] === "object" && !(obj[k] instanceof Array)) {
			clone[k] = this.cloneObject(obj[k]);
		} else {
			clone[k] = obj[k]
		}
	}
	return clone;
};

TiledCanvas.prototype.normalizeDefaults = function normalizeDefaults (target, defaults) {
	target = target || {};
	var normalized = this.cloneObject(target);
	for (var k in defaults) {
		if (typeof defaults[k] === "object" && !(defaults[k] instanceof Array)) {
			normalized[k] = this.normalizeDefaults(target[k] || {}, defaults[k]);
		} else {
			normalized[k] = target[k] || defaults[k];
		}
	}
	return normalized;
};

TiledCanvas.prototype.redraw = function redraw () {
    this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
    var startChunkX = Math.floor(this.leftTopX / this.settings.chunkSize),
        endChunkX =  Math.ceil((this.leftTopX + this.canvas.width) / this.settings.chunkSize),
        startChunkY = Math.floor(this.leftTopY / this.settings.chunkSize),
        endChunkY = Math.ceil((this.leftTopY + this.canvas.height) / this.settings.chunkSize);
    for (var chunkX = startChunkX; chunkX < endChunkX; chunkX++) {
        for (var chunkY = startChunkY; chunkY < endChunkY; chunkY++) {
            this.drawChunk(chunkX, chunkY);
        }
    }
};

TiledCanvas.prototype.drawChunk = function drawChunk (chunkX, chunkY) {
    if (this.chunks[chunkX] && this.chunks[chunkX][chunkY]) {
        this.ctx.drawImage(this.chunks[chunkX][chunkY].canvas, chunkX * this.settings.chunkSize - this.leftTopX, chunkY * this.settings.chunkSize - this.leftTopY);
    }
};

TiledCanvas.prototype.goto = function goto (x, y) {
    this.leftTopX = x;
    this.leftTopY = y;
    this.redraw();
};

TiledCanvas.prototype.execute = function execute () {
    this.executeNoRedraw();
    this.redraw();
};

TiledCanvas.prototype.executeNoRedraw = function executeNoRedraw () {
    for (var chunkX = this.affecting[0][0]; chunkX < this.affecting[1][0]; chunkX++) {
        for (var chunkY = this.affecting[0][1]; chunkY < this.affecting[1][1]; chunkY++) {
            this.executeChunk(chunkX, chunkY);
        }
    }
    this.contextQueue = [];
};

TiledCanvas.prototype.clearAll = function clearAll () {
    this.contextQueue = [];
    for (var chunkX in this.chunks) {
        for (var chunkY in this.chunks[chunkY]) {
            this.chunks[chunkX][chunkY].clearRect(0, 0, this.chunks[chunkX][chunkY].canvas.width, this.chunks[chunkX][chunkY].canvas.height);
        }
    }
};

TiledCanvas.prototype.executeChunk = function executeChunk (chunkX, chunkY) {
    this.chunks[chunkX] = this.chunks[chunkX] || [];

    this.chunks[chunkX][chunkY] = this.chunks[chunkX][chunkY] || this.newCtx(this.settings.chunkSize, this.settings.chunkSize, -chunkX * this.settings.chunkSize, -chunkY * this.settings.chunkSize);
    var ctx = this.chunks[chunkX][chunkY];

    for (var queuekey = 0; queuekey < this.contextQueue.length; queuekey++) {
        if (typeof ctx[this.contextQueue[queuekey][0]] === 'function') {
            this.executeQueueOnChunk(ctx, this.contextQueue[queuekey]);
        } else {
            ctx[this.contextQueue[queuekey][0]] = this.contextQueue[queuekey][1];
        }
    }
};

TiledCanvas.prototype.executeQueueOnChunk = function executeQueueOnChunk (ctx, args) {
    ctx[args[0]].apply(ctx, Array.prototype.slice.call(args, 1));
};

TiledCanvas.prototype.cleanup = function cleanup (chunkX, chunkY, arguments) {
    if (typeof this.cleanupFunctions[arguments[0]] === 'function') {
        return this.cleanupFunctions[arguments[0]](arguments.slice(), chunkX * this.settings.chunkSize, chunkY * this.settings.chunkSize);
    }
    return arguments;
};

TiledCanvas.prototype.drawingRegion = function (startX, startY, endX, endY, border) {
    border = border || 0;
    this.affecting[0][0] = Math.floor((Math.min(startX, endX) - border) / this.settings.chunkSize);
    this.affecting[0][1] = Math.floor((Math.min(startY, endY) - border) / this.settings.chunkSize);
    this.affecting[1][0] = Math.ceil((Math.max(endX, startX) + border) / this.settings.chunkSize);
    this.affecting[1][1] = Math.ceil((Math.max(endY, startY) + border) / this.settings.chunkSize);
};

TiledCanvas.prototype.newCtx = function newCtx (width, height, translateX, translateY) {
    var ctx = document.createElement('canvas').getContext('2d');
    ctx.canvas.width = width;
    ctx.canvas.height = height;
    ctx.translate(translateX, translateY);
    return ctx;
};

TiledCanvas.prototype.createContext = function createContext () {
    var context = {};
    var ctx = document.createElement('canvas').getContext('2d');
    for (var key in ctx) {
        if (typeof ctx[key] === 'function') {
            context[key] = function (func) {
                this.contextQueue.push(arguments);
            }.bind(this, key);
        } else if (typeof ctx[key] !== 'object') {
            context.__defineGetter__(key, function (key) {
                var ctx = this.newCtx();
                for (var queuekey = 0; queuekey < this.contextQueue.length; queuekey++) {
                    if (typeof ctx[args[0]] === 'function') {
                        ctx[args[0]].apply(ctx, args.slice(1));
                    } else {
                        ctx[args[0]] = args[1];
                    }
                }
                return ctx[key];
            }.bind(this, key));

            context.__defineSetter__(key, function (key, value) {
                this.contextQueue.push(arguments);
            }.bind(this, key));
        }
    }
    return context;
};
<|endoftext|>


<commit_before>
var readline = require('readline')
  , Q = require('q')
  , fs = require('fs')
  , path = require('path');

/**
 * Project initializer
 *
 * @constructor Init
 */

function Init() {
  this.rl = null;
  this.json = cf.DEFAULT_CONFIGS;
  this.initIntro = cf.INIT_INTRO;
  this.localesSyntax = cf.LOCALES_SYNTAX;
  this.localesWrongAnswer = cf.LOCALES_WRONG_ANSWER;
  this.localesDescription = cf.LOCALES_DESCRIPTION;
  this.defaultLocaleCode = cf.DEFAULT_LOCALE_CODE;
  this.defaultLocaleName = cf.DEFAULT_LOCALE_NAME;
  this.defaultLocaleQuestion = cf.DEFAULT_LOCALE_QUESTION;
  this.defaultLocaleWrongAnswer = cf.DEFAULT_LOCALE_WRONG_ANSWER;
  this.programmingLanguages = cf.PROGRAMMING_LANGUAGUES;
  this.chooseProgrammingLanguagePrompt = cf.CHOOSE_PROGRAMMING_LANGUAGE_PROMPT;
  this.chooseProgrammingLanguageWrongAnswer = cf.CHOOSE_PROGRAMMING_LANGUAGE_WRONG_ANSWER;
  this.programmingLanguageToDefaultSrcMap = cf.PROGRAMMING_LANGUAGUE_TO_DEFAULT_SRC_MAP;
  this.defaultOutputFolder = cf.DEFAULT_OUTPUT_FOLDER;
  this.defaultOutputFolderPrompt = cf.DEFAULT_OUTPUT_FOLDER_PROMPT;
  this.defaultOutputFolderWrongAnswer = cf.DEFAULT_OUTPUT_FOLDER_WRONG_ANSWER;
}

/**
 * Initialize
 *
 * @return {void}
 * @api public
 */

Init.prototype.init = function() {
  var _this = this;
  this._createReadlineInterface();
  this._outputIntroduction();
  this._getLocales()
  .then(function(locales) {
    _this.json.locales = locales;
    return _this._getDefaultLocale(locales);
  })
  .then(function(locale) {
    _this.json.defaultLocale = locale;
    return _this._getDefaultProgrammingLanguage();
  })
  .then(function(programmingLanguage) {
    _this.json.programmingLanguage = programmingLanguage;
    return _this._setDefaultOutput();
  })
  .then(function() {
    _this._setDefaultSrc();
    _this._writeProject();
  })
  .fail(function(err) {
    if(err) console.log(err);
  });
};

/**
 * Create readline interface
 *
 * @return {void}
 * @api private
 */

Init.prototype._createReadlineInterface = function() {
  this.rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    terminal : false
  });
};

/**
 * Output introduction to get-translation
 *
 * @return {void}
 * @api private
 */

Init.prototype._outputIntroduction = function() {
  process.stdout.write(this.initIntro);
};

/**
 * Get locales
 *
 * @return {Promise}
 * @api public
 */

Init.prototype._getLocales = function() {
  var deferred = Q.defer();
  var _this = this;
  var question =
  this.localesDescription + 'locales: (' +
  this.defaultLocaleCode + ':' + this.defaultLocaleName + ') ';
  var wrongAnswer = _this.localesWrongAnswer + question;
  var answeredWrong = false;
  (function ask() {
    if(answeredWrong) {
      question = wrongAnswer;
    }
    _this.rl.question(question, function(locales) {
      var res = {};
      if(locales === '') {
        res[_this.defaultLocaleCode] = _this.defaultLocaleName;
        return deferred.resolve(res);
      }
      if(!_this.localesSyntax.test(locales)) {
        answeredWrong = true;
        return ask();
      }
      locales.split(',').forEach(function(locale) {
        locale = locale.split(':');
        res[locale[0]] = locale[1];
      });

      deferred.resolve(res);
    });
  })();

  return deferred.promise;
};

/**
 * Get default locale
 *
 * @param {Array} locales
 * @return {Promise}
 * @api public
 */

Init.prototype._getDefaultLocale = function(locales) {
  var deferred = Q.defer()
    , codes = Object.keys(locales)
    , size = codes.length;

  if(size === 1) {
    deferred.resolve(codes[0]);
    return deferred.promise;
  }

  var options = '[', optionsEndWrap = '] ', answeredWrong = false
    , question = this.defaultLocaleQuestion, n = 1, _this = this;
  for(var code in locales) {
    question += ('[' + n + ']').lightBlue + ' - ' + locales[code] + '\n';
    options += n + ',';
    n++;
  }
  options = options.slice(0, -1) + optionsEndWrap;
  question = question.slice(0, -1) + '\n' + options.lightBlue;
  var wrongAnswer = this.defaultLocaleWrongAnswer + question;
  (function ask() {
    if(answeredWrong) {
      question = wrongAnswer;
    }
    _this.rl.question(question, function(option) {
      if(/^\d+$/.test(option)) {
        option = parseInt(option, 10) - 1;
        if(codes[option]) {
          return deferred.resolve(codes[option]);
        }
      }
      answeredWrong = true;
      ask();
    });
  })();

  return deferred.promise;
};

/**
 * Set the default programming languague by asking the
 * user
 *
 * @return {Promise}
 * @apib private
 */

Init.prototype._getDefaultProgrammingLanguage = function() {
  var _this = this;

  var deferred = Q.defer(), answeredWrong = false
    , question = this.chooseProgrammingLanguagePrompt
    , options = '[', optionsEndWrap = '] '
    , n = 1;

  for(var i = 0; i < this.programmingLanguages.length; i++) {
    question +=  ('[' + n + ']').lightBlue + ' - ' + this.programmingLanguages[i] + '\n';
    options += n + ',';
    n++;
  }
  options = options.slice(0, -1) + optionsEndWrap;
  question = question + options.lightBlue;

  var wrongAnswer = this.chooseProgrammingLanguageWrongAnswer + question;

  (function ask() {
    if(answeredWrong) {
      question = wrongAnswer;
    }
    _this.rl.question(question, function(option) {
      if(/^\d+$/.test(option)) {
        option = parseInt(option, 10) - 1;
        if(_this.programmingLanguages[option]) {
          return deferred.resolve(_this.programmingLanguages[option]);
        }
      }
      answeredWrong = true;
      ask();
    });
  })();
  return deferred.promise;
};

/**
 * Set the default output folder. Where all the compiled
 * localization files should be hosted
 *
 * @return {void}
 * @api private
 */

Init.prototype._setDefaultOutput = function() {
  var _this = this;

  var deferred = Q.defer()
    , defaultOutput, question
    , answeredWrong = false;

  if(fs.existsSync(process.cwd() + '/app')) {
    defaultOutput = 'app/' + this.defaultOutputFolder;
  }
  else {
    defaultOutput = this.defaultOutputFolder;
  }
  question = this.defaultOutputFolderPrompt + 'output: (' + defaultOutput + ') ';
  var wrongAnswer = this.defaultOutputFolderWrongAnswer + question;
  (function ask() {
    if(answeredWrong) {
      question = wrongAnswer;
    }
    _this.rl.question(question, function(option) {
      if(option === '') {
        return deferred.resolve(defaultOutput);
      }
      else {
        option = path.normalize(option);
        if(path) {
          return deferred.resolve(path);
        }
      }
      answeredWrong = true;
      ask();
    });
  })();

  return deferred.promise;
};

/**
 * Set the default source file glob pattern for the
 * project
 *
 * @return {void}
 * @api private
 */

Init.prototype._setDefaultSrc = function() {
  this.json.src =
  this.programmingLanguageToDefaultSrcMap[
    this.json.programmingLanguage
  ];
};

/**
 * Write to project after json configuration is set
 *
 * @return {void}
 * @api private
 */

Init.prototype._writeProject = function() {
  var cwd = process.cwd()
    , file = cwd + '/gt.json'
    , folder = cwd + '/.gt';

  if(!fs.existsSync(file))
    fs.writeFile(file, JSON.stringify(this.json, null, 2));

  if(!fs.existsSync(folder))
    fs.mkdir(folder);
};

/**
 * Export instance
 */

module.exports = new Init;

/**
 * Exports constructor
 */

module.exports.Init = Init;
<commit_msg>Fixed typo<commit_after>var readline = require('readline')
  , Q = require('q')
  , fs = require('fs')
  , path = require('path');

/**
 * Project initializer
 *
 * @constructor Init
 */

function Init() {
  this.rl = null;
  this.json = cf.DEFAULT_CONFIGS;
  this.initIntro = cf.INIT_INTRO;
  this.localesSyntax = cf.LOCALES_SYNTAX;
  this.localesWrongAnswer = cf.LOCALES_WRONG_ANSWER;
  this.localesDescription = cf.LOCALES_DESCRIPTION;
  this.defaultLocaleCode = cf.DEFAULT_LOCALE_CODE;
  this.defaultLocaleName = cf.DEFAULT_LOCALE_NAME;
  this.defaultLocaleQuestion = cf.DEFAULT_LOCALE_QUESTION;
  this.defaultLocaleWrongAnswer = cf.DEFAULT_LOCALE_WRONG_ANSWER;
  this.programmingLanguages = cf.PROGRAMMING_LANGUAGUES;
  this.chooseProgrammingLanguagePrompt = cf.CHOOSE_PROGRAMMING_LANGUAGE_PROMPT;
  this.chooseProgrammingLanguageWrongAnswer = cf.CHOOSE_PROGRAMMING_LANGUAGE_WRONG_ANSWER;
  this.programmingLanguageToDefaultSrcMap = cf.PROGRAMMING_LANGUAGUE_TO_DEFAULT_SRC_MAP;
  this.defaultOutputFolder = cf.DEFAULT_OUTPUT_FOLDER;
  this.defaultOutputFolderPrompt = cf.DEFAULT_OUTPUT_FOLDER_PROMPT;
  this.defaultOutputFolderWrongAnswer = cf.DEFAULT_OUTPUT_FOLDER_WRONG_ANSWER;
}

/**
 * Initialize
 *
 * @return {void}
 * @api public
 */

Init.prototype.init = function() {
  var _this = this;
  this._createReadlineInterface();
  this._outputIntroduction();
  this._getLocales()
  .then(function(locales) {
    _this.json.locales = locales;
    return _this._getDefaultLocale(locales);
  })
  .then(function(locale) {
    _this.json.defaultLocale = locale;
    return _this._getDefaultProgrammingLanguage();
  })
  .then(function(programmingLanguage) {
    _this.json.programmingLanguage = programmingLanguage;
    return _this._setDefaultOutput();
  })
  .then(function() {
    _this._setDefaultSrc();
    _this._writeProject();
  })
  .fail(function(err) {
    if(err) console.log(err);
  });
};

/**
 * Create readline interface
 *
 * @return {void}
 * @api private
 */

Init.prototype._createReadlineInterface = function() {
  this.rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    terminal : false
  });
};

/**
 * Output introduction to get-translation
 *
 * @return {void}
 * @api private
 */

Init.prototype._outputIntroduction = function() {
  process.stdout.write(this.initIntro);
};

/**
 * Get locales
 *
 * @return {Promise}
 * @api public
 */

Init.prototype._getLocales = function() {
  var deferred = Q.defer();
  var _this = this;
  var question =
  this.localesDescription + 'locales: (' +
  this.defaultLocaleCode + ':' + this.defaultLocaleName + ') ';
  var wrongAnswer = _this.localesWrongAnswer + question;
  var answeredWrong = false;
  (function ask() {
    if(answeredWrong) {
      question = wrongAnswer;
    }
    _this.rl.question(question, function(locales) {
      var res = {};
      if(locales === '') {
        res[_this.defaultLocaleCode] = _this.defaultLocaleName;
        return deferred.resolve(res);
      }
      if(!_this.localesSyntax.test(locales)) {
        answeredWrong = true;
        return ask();
      }
      locales.split(',').forEach(function(locale) {
        locale = locale.split(':');
        res[locale[0]] = locale[1];
      });

      deferred.resolve(res);
    });
  })();

  return deferred.promise;
};

/**
 * Get default locale
 *
 * @param {Array} locales
 * @return {Promise}
 * @api public
 */

Init.prototype._getDefaultLocale = function(locales) {
  var deferred = Q.defer()
    , codes = Object.keys(locales)
    , size = codes.length;

  if(size === 1) {
    deferred.resolve(codes[0]);
    return deferred.promise;
  }

  var options = '[', optionsEndWrap = '] ', answeredWrong = false
    , question = this.defaultLocaleQuestion, n = 1, _this = this;
  for(var code in locales) {
    question += ('[' + n + ']').lightBlue + ' - ' + locales[code] + '\n';
    options += n + ',';
    n++;
  }
  options = options.slice(0, -1) + optionsEndWrap;
  question = question.slice(0, -1) + '\n' + options.lightBlue;
  var wrongAnswer = this.defaultLocaleWrongAnswer + question;
  (function ask() {
    if(answeredWrong) {
      question = wrongAnswer;
    }
    _this.rl.question(question, function(option) {
      if(/^\d+$/.test(option)) {
        option = parseInt(option, 10) - 1;
        if(codes[option]) {
          return deferred.resolve(codes[option]);
        }
      }
      answeredWrong = true;
      ask();
    });
  })();

  return deferred.promise;
};

/**
 * Set the default programming language by asking the
 * user
 *
 * @return {Promise}
 * @api private
 */

Init.prototype._getDefaultProgrammingLanguage = function() {
  var _this = this;

  var deferred = Q.defer(), answeredWrong = false
    , question = this.chooseProgrammingLanguagePrompt
    , options = '[', optionsEndWrap = '] '
    , n = 1;

  for(var i = 0; i < this.programmingLanguages.length; i++) {
    question +=  ('[' + n + ']').lightBlue + ' - ' + this.programmingLanguages[i] + '\n';
    options += n + ',';
    n++;
  }
  options = options.slice(0, -1) + optionsEndWrap;
  question = question + options.lightBlue;

  var wrongAnswer = this.chooseProgrammingLanguageWrongAnswer + question;

  (function ask() {
    if(answeredWrong) {
      question = wrongAnswer;
    }
    _this.rl.question(question, function(option) {
      if(/^\d+$/.test(option)) {
        option = parseInt(option, 10) - 1;
        if(_this.programmingLanguages[option]) {
          return deferred.resolve(_this.programmingLanguages[option]);
        }
      }
      answeredWrong = true;
      ask();
    });
  })();
  return deferred.promise;
};

/**
 * Set the default output folder. Where all the compiled
 * localization files should be hosted
 *
 * @return {void}
 * @api private
 */

Init.prototype._setDefaultOutput = function() {
  var _this = this;

  var deferred = Q.defer()
    , defaultOutput, question
    , answeredWrong = false;

  if(fs.existsSync(process.cwd() + '/app')) {
    defaultOutput = 'app/' + this.defaultOutputFolder;
  }
  else {
    defaultOutput = this.defaultOutputFolder;
  }
  question = this.defaultOutputFolderPrompt + 'output: (' + defaultOutput + ') ';
  var wrongAnswer = this.defaultOutputFolderWrongAnswer + question;
  (function ask() {
    if(answeredWrong) {
      question = wrongAnswer;
    }
    _this.rl.question(question, function(option) {
      if(option === '') {
        return deferred.resolve(defaultOutput);
      }
      else {
        option = path.normalize(option);
        if(path) {
          return deferred.resolve(path);
        }
      }
      answeredWrong = true;
      ask();
    });
  })();

  return deferred.promise;
};

/**
 * Set the default source file glob pattern for the
 * project
 *
 * @return {void}
 * @api private
 */

Init.prototype._setDefaultSrc = function() {
  this.json.src =
  this.programmingLanguageToDefaultSrcMap[
    this.json.programmingLanguage
  ];
};

/**
 * Write to project after json configuration is set
 *
 * @return {void}
 * @api private
 */

Init.prototype._writeProject = function() {
  var cwd = process.cwd()
    , file = cwd + '/gt.json'
    , folder = cwd + '/.gt';

  if(!fs.existsSync(file))
    fs.writeFile(file, JSON.stringify(this.json, null, 2));

  if(!fs.existsSync(folder))
    fs.mkdir(folder);
};

/**
 * Export instance
 */

module.exports = new Init;

/**
 * Exports constructor
 */

module.exports.Init = Init;
<|endoftext|>