<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import {
  camelizeKeys,
  // hyphenateKeys,
  extend,
  extendTruthy,
  trimComment,
  normalizeStyle,
  autoPrefix
} from '../utils'
import { tagBegin, tagEnd } from '../utils/perf'
/* istanbul ignore next */

/**
 * get scoped class style map from stylesheets in <head>.
 */
export function getHeadStyleMap () {
  if (process.env.NODE_ENV === 'development') {
    tagBegin('getHeadStyleMap')
  }
  const needToRemoveStyleSheetNodes = []
  const res = Array.from(document.styleSheets || [])
    .reduce((pre, styleSheet) => {
      /**
       * why not using styleSheet.rules || styleSheet.cssRules to get css rules ?
       * because weex's components defined non-standard style attributes, which is
       * auto ignored when access rule.cssText.
       */
      if (!styleSheet.cssRules
        || (styleSheet.ownerNode.tagName.toLowerCase() === 'link')
        || !styleSheet.ownerNode.textContent) {
        /**
         * no rules, or in a link. just ignore this. probably a link stylesheet.
         */
        return pre
      }
      const strArr = trimComment(styleSheet.ownerNode.textContent.trim()).split(/}/)
      const len = strArr.length
      const rules = []
      for (let i = 0; i < len; i++) {
        const str = strArr[i]
        if (!str || str.match(/^\s*$/)) {
          continue
        }
        /**
         * should match these cases:
         * .a[data-v-xxx] { color: red }
         * .a[data-v-xxx], .b[data-v-xxx] { color: red; }
         *
         * should not match these cases:
         * .a { color: red; }
         * etc.
         */
        const match = str.match(/((?:,?\s*\.[\w-]+\[data-v-\w+\](?::\w+)?)+)\s*({[^}]+)/)
        if (!match) {
          // not the vue static class styles map. so acquire no rules for this styleSheet.
          // just jump through this styleSheet and go to analyzing next.
          return pre
        }
        const clsNms = match[1].split(',').map(n => n.trim())
        const cssText = match[2].replace(/[{}]/g, '').trim()
        let clsNmsIdx = 0
        const clsNmsLen = clsNms.length
        while (clsNmsIdx < clsNmsLen) {
          rules.push({
            selectorText: clsNms[clsNmsIdx],
            cssText
          })
          clsNmsIdx++
        }
      }
      Array.from(rules).forEach(rule => {
        const selector = rule.selectorText || ''
        const styleObj = trimComment(rule.cssText).split(';')
          .reduce((styleObj, statement) => {
            statement = statement.trim()
            if (statement && statement.indexOf('/*') <= -1) {
              const resArr = statement.split(':').map((part) => part.trim())
              styleObj[resArr[0]] = resArr[1]
            }
            return styleObj
          }, {})
        const res = pre[selector]
        if (!res) {
          pre[selector] = styleObj
        }
        else {
          extend(pre[selector], styleObj)
        }
      })
      /**
       * remove this styleSheet node since it's in the styleMap already. And this style
       * should only be fetched and used from styleMap to generate the final combined
       * component style, not from the stylesheet itself.
       */
      needToRemoveStyleSheetNodes.push(styleSheet.ownerNode)
      return pre
    }, {})
  if (!window._no_remove_style_sheets) {
    needToRemoveStyleSheetNodes.forEach(function (node) {
      node.parentNode.removeChild(node)
    })
  }
  else if (process.env.NODE_ENV === 'development') {
    console.warn(`[vue-render] you've defined '_no_remove_style_sheets' and the v-data-xx stylesheets will not be removed.`)
  }
  if (process.env.NODE_ENV === 'development') {
    tagEnd('getHeadStyleMap')
  }
  return res
}

// export function getScopeIds (context) {
//   const arr = []
//   let ctx = context
//   let scopeId
//   while (ctx) {
//     scopeId = ctx.$options._scopeId
//     scopeId && arr.push(scopeId)
//     ctx = ctx.$options.parent
//   }
//   return arr
// }

export function getScopeId (vnode) {
  return vnode.context.$options._scopeId
}

/**
 * get style in <style scoped> tags for this component.
 */
export function getScopeStyle (vnode, classNames) {
  const scopeId = getScopeId(vnode)
  const style = {}
  const styleMap = weex.styleMap
  let clsNmsIdx = 0
  const clsNmsLen = classNames.length
  while (clsNmsIdx < clsNmsLen) {
    const cls = `.${classNames[clsNmsIdx]}[${scopeId}]`
    const map = styleMap[cls]
    map && extendTruthy(style, map)
    clsNmsIdx++
  }
  return camelizeKeys(style)
}

function getStyle (vnode, extract) {
  const data = vnode.data || {}
  const staticClassNames = (typeof data.staticClass === 'string') ? data.staticClass.split(' ') : (data.staticClass || [])
  const classNames = (typeof data.class === 'string') ? data.class.split(' ') : (data.class || [])
  const clsNms = staticClassNames.concat(classNames)
  const style = normalizeStyle(getScopeStyle(vnode, clsNms))
  /**
   * cache static style and bind style.
   * cached staticStyle (including style and staticStyle) has already been normalized
   * in $processStyle. So there's no need to normalize it again.
   */
  if (!data.cached) {
    // cache staticStyle once in the beginning.
    data.cached = extendTruthy({}, data.staticStyle)
  }
  // cache binding style every time since the binding style is variable.
  extendTruthy(data.cached, data.style)
  extend(style, data.cached)
  data.staticStyle = style
  if (extract) {
    delete data.staticStyle
    delete data.style
  }
  return style
}

/**
 * get style merged with static styles, binding styles, and scoped class styles,
 * with keys in camelcase.
 */
export function getComponentStyle (context, extract) {
  if (!context.$vnode) {
    if (process.env.NODE_ENV === 'development') {
      return console.error('[vue-render] getComponentStyle failed: no $vnode in context.')
    }
    return {}
  }
  let style = {}
  let vnode = context.$vnode
  while (vnode) {
    extend(style, getStyle(vnode, extract))
    vnode = vnode.parent
  }
  style = autoPrefix(style)
  /**
   * when prefixed value is a array, it should be applied to element
   * during the next tick.
   * e.g.
   *  background-image:  linear-gradient(to top,#f5fefd,#ffffff);
   *  will generate:
   *  {
   *    backgroundImage: [
   *      "-webkit-linear-gradient(to top,#f5fefd,#ffffff)",
   *      "-moz-linear-gradient(to top,#f5fefd,#ffffff)",
   *      "linear-gradient(to top,#f5fefd,#ffffff)"]
   *  }
   */
  for (const k in style) {
    if (Array.isArray(style[k])) {
      const vals = style[k]
      context.$nextTick(function () {
        const el = context.$el
        if (el) {
          for (let i = 0; i < vals.length; i++) {
            el.style[k] = vals[i]
          }
        }
      })
      delete style[k]
    }
  }
  return style
  // return addPrefix(normalizeStyle(style))
}

export function extractComponentStyle (context) {
  return getComponentStyle(context, true)
}

/**
 * get { width, height } (size) of current component from components' styles.
 */
export function getSize (context) {
  if (!context.$vnode) {
    if (process.env.NODE_ENV === 'development') {
      return console.error('[vue-render] getComponentStyle failed: no $vnode in context.')
    }
    return {}
  }
  const data = context.$vnode.data
  const wh = {}
  const classes = typeof data.class === 'string' ? data.class.split(' ') : (data.class || [])
  const staticClass = typeof data.staticClass === 'string' ? data.staticClass.split(' ') : (data.class || [])
  const clsNms = staticClass.concat(classes)
  function extendWHFrom (to, from) {
    if (!from) { return }
    from.width && (to.width = from.width)
    from.height && (to.height = from.height)
  }
  extendWHFrom(wh, this._getScopeStyle(clsNms))
  extendWHFrom(wh, data.staticStyle)
  extendWHFrom(wh, data.style)
  return wh
}
<commit_msg>* [html5] in case the styleMap not exists.<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
import {
  camelizeKeys,
  // hyphenateKeys,
  extend,
  extendTruthy,
  trimComment,
  normalizeStyle,
  autoPrefix
} from '../utils'
import { tagBegin, tagEnd } from '../utils/perf'
/* istanbul ignore next */

/**
 * get scoped class style map from stylesheets in <head>.
 */
export function getHeadStyleMap () {
  if (process.env.NODE_ENV === 'development') {
    tagBegin('getHeadStyleMap')
  }
  const needToRemoveStyleSheetNodes = []
  const res = Array.from(document.styleSheets || [])
    .reduce((pre, styleSheet) => {
      /**
       * why not using styleSheet.rules || styleSheet.cssRules to get css rules ?
       * because weex's components defined non-standard style attributes, which is
       * auto ignored when access rule.cssText.
       */
      if (!styleSheet.cssRules
        || (styleSheet.ownerNode.tagName.toLowerCase() === 'link')
        || !styleSheet.ownerNode.textContent) {
        /**
         * no rules, or in a link. just ignore this. probably a link stylesheet.
         */
        return pre
      }
      const strArr = trimComment(styleSheet.ownerNode.textContent.trim()).split(/}/)
      const len = strArr.length
      const rules = []
      for (let i = 0; i < len; i++) {
        const str = strArr[i]
        if (!str || str.match(/^\s*$/)) {
          continue
        }
        /**
         * should match these cases:
         * .a[data-v-xxx] { color: red }
         * .a[data-v-xxx], .b[data-v-xxx] { color: red; }
         *
         * should not match these cases:
         * .a { color: red; }
         * etc.
         */
        const match = str.match(/((?:,?\s*\.[\w-]+\[data-v-\w+\](?::\w+)?)+)\s*({[^}]+)/)
        if (!match) {
          // not the vue static class styles map. so acquire no rules for this styleSheet.
          // just jump through this styleSheet and go to analyzing next.
          return pre
        }
        const clsNms = match[1].split(',').map(n => n.trim())
        const cssText = match[2].replace(/[{}]/g, '').trim()
        let clsNmsIdx = 0
        const clsNmsLen = clsNms.length
        while (clsNmsIdx < clsNmsLen) {
          rules.push({
            selectorText: clsNms[clsNmsIdx],
            cssText
          })
          clsNmsIdx++
        }
      }
      Array.from(rules).forEach(rule => {
        const selector = rule.selectorText || ''
        const styleObj = trimComment(rule.cssText).split(';')
          .reduce((styleObj, statement) => {
            statement = statement.trim()
            if (statement && statement.indexOf('/*') <= -1) {
              const resArr = statement.split(':').map((part) => part.trim())
              styleObj[resArr[0]] = resArr[1]
            }
            return styleObj
          }, {})
        const res = pre[selector]
        if (!res) {
          pre[selector] = styleObj
        }
        else {
          extend(pre[selector], styleObj)
        }
      })
      /**
       * remove this styleSheet node since it's in the styleMap already. And this style
       * should only be fetched and used from styleMap to generate the final combined
       * component style, not from the stylesheet itself.
       */
      needToRemoveStyleSheetNodes.push(styleSheet.ownerNode)
      return pre
    }, {})
  if (!window._no_remove_style_sheets) {
    needToRemoveStyleSheetNodes.forEach(function (node) {
      node.parentNode.removeChild(node)
    })
  }
  else if (process.env.NODE_ENV === 'development') {
    console.warn(`[vue-render] you've defined '_no_remove_style_sheets' and the v-data-xx stylesheets will not be removed.`)
  }
  if (process.env.NODE_ENV === 'development') {
    tagEnd('getHeadStyleMap')
  }
  return res
}

// export function getScopeIds (context) {
//   const arr = []
//   let ctx = context
//   let scopeId
//   while (ctx) {
//     scopeId = ctx.$options._scopeId
//     scopeId && arr.push(scopeId)
//     ctx = ctx.$options.parent
//   }
//   return arr
// }

export function getScopeId (vnode) {
  return vnode.context.$options._scopeId
}

/**
 * get style in <style scoped> tags for this component.
 */
export function getScopeStyle (vnode, classNames) {
  const scopeId = getScopeId(vnode)
  const style = {}
  const styleMap = weex.styleMap || {}
  let clsNmsIdx = 0
  const clsNmsLen = classNames.length
  while (clsNmsIdx < clsNmsLen) {
    const cls = `.${classNames[clsNmsIdx]}[${scopeId}]`
    const map = styleMap[cls]
    map && extendTruthy(style, map)
    clsNmsIdx++
  }
  return camelizeKeys(style)
}

function getStyle (vnode, extract) {
  const data = vnode.data || {}
  const staticClassNames = (typeof data.staticClass === 'string') ? data.staticClass.split(' ') : (data.staticClass || [])
  const classNames = (typeof data.class === 'string') ? data.class.split(' ') : (data.class || [])
  const clsNms = staticClassNames.concat(classNames)
  const style = normalizeStyle(getScopeStyle(vnode, clsNms))
  /**
   * cache static style and bind style.
   * cached staticStyle (including style and staticStyle) has already been normalized
   * in $processStyle. So there's no need to normalize it again.
   */
  if (!data.cached) {
    // cache staticStyle once in the beginning.
    data.cached = extendTruthy({}, data.staticStyle)
  }
  // cache binding style every time since the binding style is variable.
  extendTruthy(data.cached, data.style)
  extend(style, data.cached)
  data.staticStyle = style
  if (extract) {
    delete data.staticStyle
    delete data.style
  }
  return style
}

/**
 * get style merged with static styles, binding styles, and scoped class styles,
 * with keys in camelcase.
 */
export function getComponentStyle (context, extract) {
  if (!context.$vnode) {
    if (process.env.NODE_ENV === 'development') {
      return console.error('[vue-render] getComponentStyle failed: no $vnode in context.')
    }
    return {}
  }
  let style = {}
  let vnode = context.$vnode
  while (vnode) {
    extend(style, getStyle(vnode, extract))
    vnode = vnode.parent
  }
  style = autoPrefix(style)
  /**
   * when prefixed value is a array, it should be applied to element
   * during the next tick.
   * e.g.
   *  background-image:  linear-gradient(to top,#f5fefd,#ffffff);
   *  will generate:
   *  {
   *    backgroundImage: [
   *      "-webkit-linear-gradient(to top,#f5fefd,#ffffff)",
   *      "-moz-linear-gradient(to top,#f5fefd,#ffffff)",
   *      "linear-gradient(to top,#f5fefd,#ffffff)"]
   *  }
   */
  for (const k in style) {
    if (Array.isArray(style[k])) {
      const vals = style[k]
      context.$nextTick(function () {
        const el = context.$el
        if (el) {
          for (let i = 0; i < vals.length; i++) {
            el.style[k] = vals[i]
          }
        }
      })
      delete style[k]
    }
  }
  return style
  // return addPrefix(normalizeStyle(style))
}

export function extractComponentStyle (context) {
  return getComponentStyle(context, true)
}

/**
 * get { width, height } (size) of current component from components' styles.
 */
export function getSize (context) {
  if (!context.$vnode) {
    if (process.env.NODE_ENV === 'development') {
      return console.error('[vue-render] getComponentStyle failed: no $vnode in context.')
    }
    return {}
  }
  const data = context.$vnode.data
  const wh = {}
  const classes = typeof data.class === 'string' ? data.class.split(' ') : (data.class || [])
  const staticClass = typeof data.staticClass === 'string' ? data.staticClass.split(' ') : (data.class || [])
  const clsNms = staticClass.concat(classes)
  function extendWHFrom (to, from) {
    if (!from) { return }
    from.width && (to.width = from.width)
    from.height && (to.height = from.height)
  }
  extendWHFrom(wh, this._getScopeStyle(clsNms))
  extendWHFrom(wh, data.staticStyle)
  extendWHFrom(wh, data.style)
  return wh
}
<|endoftext|>


<commit_before>Bullet.CachedCanvasRenderer = function(){

    this.cache = {};
    this.element = document.createElement('canvas');
    this.context = this.element.getContext('2d');

    this.element.setAttribute('class', 'renderer');
    this.element.width = 1280;
    this.element.height = 960;

}

Bullet.CachedCanvasRenderer.prototype = {

    getElement: function(){
        return this.element;
    },

    generateCircle: function(colorBase36, pixelSize){

        var canvas = document.createElement('canvas'),
            context = canvas.getContext('2d');

        canvas.width = canvas.height = pixelSize;

        var base = Bullet.Options.colorBase;

        var rgb = Bullet.Util.base36toRgb(colorBase36, base),
            rgbString = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";

        var rasterSize = Bullet.Util.getRasterWidth(colorBase36, base) * ((pixelSize) / 2),
            xOnCanvas = yOnCanvas = Math.floor(pixelSize / 2);

        context.beginPath();
        context.fillRect(0, 0, pixelSize, pixelSize);
        context.arc(xOnCanvas, yOnCanvas, rasterSize, 0, Math.PI * 2, false);
        context.fillStyle = rgbString;
        context.fill();
        context.closePath();

        return canvas;

    },
/*
    _partialRender: function(pixelColor, pixelIndexArray, cols, pixelSize, pixelRadius){

    },*/

    render: function(encoderOutput){

        var matrix = encoderOutput.matrix,
            cols = encoderOutput.metadata.cols,
            pixelSize = Math.floor(this.element.width / cols),
            pixelRadius = pixelSize / 2;

        pixelSize += pixelSize % 2;

        for(var pixelColor in matrix) {

            var pixelIndexArray = matrix[pixelColor];

            var context = this.context,
                cache = this.cache;

            var sourcePixel = cache[pixelColor];

            if (!sourcePixel) {
                sourcePixel = this.cache[pixelColor] = this.generateCircle(pixelColor, pixelSize);
            }

            var row, col, xOffset;

            for (var p = 0; p < pixelIndexArray.length; p++) {

                var pixelIndex = pixelIndexArray[p];

                // decode pixelIndex (find row and col)
                row = Math.floor(pixelIndex / cols);
                col = pixelIndex % cols;

                if (row % 2 === 0) {
                    xOffset = pixelRadius
                }

                var xOnCanvas = (col * pixelSize) + xOffset,
                    yOnCanvas = row * pixelSize;

                context.drawImage(sourcePixel, 0, 0, pixelSize, pixelSize, xOnCanvas, yOnCanvas, pixelSize, pixelSize);

                xOffset = 0;

            }

            //this._partialRender(pixelColor, pixelIndexArray, cols, pixelSize, pixelRadius)

        }

    }
};<commit_msg>Round beats Floor<commit_after>Bullet.CachedCanvasRenderer = function(){

    this.cache = {};
    this.element = document.createElement('canvas');
    this.context = this.element.getContext('2d');

    this.element.setAttribute('class', 'renderer');
    this.element.width = 1280;
    this.element.height = 960;

}

Bullet.CachedCanvasRenderer.prototype = {

    getElement: function(){
        return this.element;
    },

    generateCircle: function(colorBase36, pixelSize){

        var canvas = document.createElement('canvas'),
            context = canvas.getContext('2d');

        canvas.width = canvas.height = pixelSize;

        var base = Bullet.Options.colorBase;

        var rgb = Bullet.Util.base36toRgb(colorBase36, base),
            rgbString = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";

        var rasterSize = Bullet.Util.getRasterWidth(colorBase36, base) * ((pixelSize) / 2),
            xOnCanvas = yOnCanvas = Math.floor(pixelSize / 2);

        context.beginPath();
        context.fillRect(0, 0, pixelSize, pixelSize);
        context.arc(xOnCanvas, yOnCanvas, rasterSize, 0, Math.PI * 2, false);
        context.fillStyle = rgbString;
        context.fill();
        context.closePath();

        return canvas;

    },
/*
    _partialRender: function(pixelColor, pixelIndexArray, cols, pixelSize, pixelRadius){

    },*/

    render: function(encoderOutput){

        var matrix = encoderOutput.matrix,
            cols = encoderOutput.metadata.cols,
            pixelSize = Math.round(this.element.width / cols),
            pixelRadius = pixelSize / 2;

        pixelSize += pixelSize % 2;

        for(var pixelColor in matrix) {

            var pixelIndexArray = matrix[pixelColor];

            var context = this.context,
                cache = this.cache;

            var sourcePixel = cache[pixelColor];

            if (!sourcePixel) {
                sourcePixel = this.cache[pixelColor] = this.generateCircle(pixelColor, pixelSize);
            }

            var row, col, xOffset;

            for (var p = 0; p < pixelIndexArray.length; p++) {

                var pixelIndex = pixelIndexArray[p];

                // decode pixelIndex (find row and col)
                row = Math.floor(pixelIndex / cols);
                col = pixelIndex % cols;

                if (row % 2 === 0) {
                    xOffset = pixelRadius
                }

                var xOnCanvas = (col * pixelSize) + xOffset,
                    yOnCanvas = row * pixelSize;

                context.drawImage(sourcePixel, 0, 0, pixelSize, pixelSize, xOnCanvas, yOnCanvas, pixelSize, pixelSize);

                xOffset = 0;

            }

            //this._partialRender(pixelColor, pixelIndexArray, cols, pixelSize, pixelRadius)

        }

    }
};<|endoftext|>


<commit_before>// app/routes/pixels.js rsub

// database schemas
var Project 	= require('../models/project');
var Clientel 	= require('../models/client');	// weird name cause 'Client' is restricted name
var User  	= require('../models/user');
var File 	= require('../models/file');
var Layers 	= require('../models/layer');

// file handling
var fs 		= require('fs-extra');
var utf8 	= require("utf8");

// utils
var async 	= require('async');
var util 	= require('util');
var request 	= require('request');
var uuid 	= require('node-uuid');
var _ 		= require('lodash-node');
var zlib 	= require('zlib');
var uploadProgress = require('node-upload-progress');
var crypto      = require('crypto');
var nodemailer  = require('nodemailer');
var gm 		= require('gm');

// globals
var IMAGEFOLDER = '/data/images/';

// function exports
module.exports = pixels = {

	handleImage : function (path, callback) {

		var entry = {};
		var file = path;
		var ops = {};

		ops.dimensions = function (cb) {
			// get image size
			pixels.getImageSize(file, cb);
		};

		ops.identity = function (cb) {
			// get exif size
			pixels.getIdentify(file, cb);
		};

		ops.dataSize = function (cb) {
			// get file size in bytes
			pixels.getFileSize(file, cb);
		};

		// move raw file into /images/ folder
		ops.rawFile = function (cb) {

			// copy raw file
			pixels.copyRawFile(file, cb);
		};

		// run all ops async in series
		async.series(ops, function (err, results) {
			if (err) console.error('_processImage err: ', err);

			var exif 	= results.identity,
			    dimensions 	= results.dimensions,
			    dataSize 	= results.dataSize,
			    file 	= results.rawFile;

			entry.data 		     = entry.data || {};
			entry.data.image 	     = entry.data.image || {};
			entry.data.image.dimensions  = dimensions;
			entry.dataSize        	     = dataSize;
			entry.data.image.file        = file;

			if (exif) {
				entry.data.image.created     = pixels.getExif.created(exif);
				entry.data.image.gps         = pixels.getExif.gps(exif);
				entry.data.image.cameraType  = pixels.getExif.cameraType(exif);
				entry.data.image.orientation = pixels.getExif.orientation(exif);
			}

			console.log('**********************************')
			console.log('* fn: crunch._processImage: * DONE! entry: ', entry);
			console.log('* results: ', results);
			console.log('**********************************')

			// return results to whatever callback
			callback(null, entry);
		});

	},

	// process images straight after upload
	_processImage : function (entry, callback) {
		console.log('**********************************')
		console.log('* fn: crunch._processImage * entry: ', entry);
		console.log('**********************************')

		var file = entry.permanentPath,
		    ops = {};

		ops.dimensions = function (cb) {
			// get image size
			pixels.getImageSize(file, cb);
		};

		ops.identity = function (cb) {
			// get exif size
			pixels.getIdentify(file, cb);
		};

		ops.dataSize = function (cb) {
			// get file size in bytes
			pixels.getFileSize(file, cb);
		};

		// move raw file into /images/ folder
		ops.rawFile = function (cb) {

			// move raw file
			pixels.moveRawFile(file, cb);
		};

		// run all ops async in series
		async.series(ops, function (err, results) {
			if (err) console.error('_processImage err: ', err);

			console.log('*** _processImage async DONE: reults: ', results);

			var exif 	= results.identity,
			    dimensions 	= results.dimensions,
			    dataSize 	= results.dataSize,
			    file 	= results.rawFile;

			entry.data.image 	     = entry.data.image || {};
			entry.data.image.dimensions  = dimensions;
			entry.dataSize        	     = dataSize;
			entry.data.image.created     = pixels.getExif.created(exif);
			entry.data.image.gps         = pixels.getExif.gps(exif);
			entry.data.image.cameraType  = pixels.getExif.cameraType(exif);
			entry.data.image.orientation = pixels.getExif.orientation(exif);
			entry.data.image.file        = file;

			console.log('**********************************')
			console.log('* fn: crunch._processImage: * DONE! entry: ', entry);
			console.log('* results: ', results);
			console.log('**********************************')

			// return results to whatever callback
			callback(err, entry);
		});
	},

	// helper fn to parse exif
	getExif : {
		created : function (exif) {
			if (!exif) return;
			var time = '';
			var profile = exif['Profile-EXIF']
			if (profile) time = profile['Date Time'];			// todo: other date formats
			if (!time) return;
			var s = time.split(/[-: ]/);
 			var date = new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5]);
			time = date.toJSON();
			return time;
		},

		gps : function (exif) {
			var profile = exif['Profile-EXIF'];
			if (!profile) return;

			// get numbers
			var altitude 	= pixels.getExif.getAltitude(profile);
			var direction 	= pixels.getExif.getDirection(profile);
			var coords 	= pixels.getExif.getCoords(profile);

			var gps = {
				lat : coords.lat,
				lng : coords.lng,
				alt : altitude,
				dir : direction
			}
	                return gps;
		},

		cameraType : function (exif) {
			var cameraType = '';
			var profile = exif['Profile-EXIF']
			if (profile) cameraType = profile['Model'];
			return cameraType;
		},

		orientation : function (exif) {
			var orientation = -1;
			var orientations = {
				'topleft' 	: 1,
				'bottomright'	: 3,		// todo: BottomImageRight
				'righttop'	: 6,
				'leftbottom'	: 8
			}

			// from exif in int format
			var profile = exif['Profile-EXIF'];
			if (profile) {
				orientation = profile.orientation;
				if (orientation) return orientation;
			}

			// try from root orientation
			var o = exif['Orientation'];
			if (o) {
				var text = o.toLowerCase();
				orientation = orientations[text];
				if (orientation) return orientation;
			}

			return -1;
		},

		getAltitude : function (profile) {
			var gpsalt = profile['GPS Altitude'];
			if (!gpsalt) return -1;
			var alt = gpsalt.split('/');
			if (!alt) return -1;
			var x = alt[0];
			var y = alt[1];
			var altitude = parseInt(x) / parseInt(y);
			console.log('Altitude!: ', altitude);
			return altitude;
		},

		getDirection : function (profile) {
			var gpsdir = profile['GPS Img Direction'];
			if (!gpsdir) return -1;
			var dir = gpsdir.split('/');
			if (!dir) return -1;
			var x = dir[0];
			var y = dir[1];
			var direction = parseInt(x) / parseInt(y);
			console.log('Direction!: ', direction);
			return direction;
		},

		getCoords : function(exif) {
			// https://github.com/Turistforeningen/dms2dec.js/blob/master/lib/dms2dec.js

			var lat = exif.GPSLatitude || exif['GPS Latitude'];
	                var lon = exif.GPSLongitude || exif['GPS Longitude'];
	 		var latRef = exif.GPSLatitudeRef || exif['GPS Latitude Ref'] || "N";
	                var lonRef = exif.GPSLongitudeRef || exif['GPS Longitude Ref'] || "W";

	                if (!lat || !lon) return false;

			var ref = {'N': 1, 'E': 1, 'S': -1, 'W': -1};
			var i;

			if (typeof lat === 'string') {
				lat = lat.split(',');
			}

			if (typeof lon === 'string') {
				lon = lon.split(',');
			}

			for (i = 0; i < lat.length; i++) {
				if (typeof lat[i] === 'string') {
					lat[i] = lat[i].replace(/^\s+|\s+$/g,'').split('/');
					lat[i] = parseInt(lat[i][0], 10) / parseInt(lat[i][1], 10);
				}
			}

			for (i = 0; i < lon.length; i++) {
				if (typeof lon[i] === 'string') {
					lon[i] = lon[i].replace(/^\s+|\s+$/g,'').split('/');
					lon[i] = parseInt(lon[i][0], 10) / parseInt(lon[i][1], 10);
				}
			}

			lat = (lat[0] + (lat[1] / 60) + (lat[2] / 3600)) * ref[latRef];
			lon = (lon[0] + (lon[1] / 60) + (lon[2] / 3600)) * ref[lonRef];

			return {lat : lat, lng : lon}
		},

	},

	getImageSize : function (path, callback) {
		gm(path)
		.size(function (err, size) {
			callback(err, size);
		});
	},

	getFileSize : function (path, callback) {
 		fs.stat(path, function (err, stats) {
 			callback(err, stats.size);
 		});
	},

	// move raw file to /images/ folder
	moveRawFile : function (oldPath, callback) {

		var newFile = 'image-raw-' + uuid.v4();
		var newPath = IMAGEFOLDER + newFile;

		// move file
		fs.rename(oldPath, newPath, function (err) {
			if (err) console.log('fs.rename error: ', err);
			callback(err, newFile);
		});
	},

	// copy raw file to /images/ folder
	copyRawFile : function (oldPath, callback) {

		var newFile = 'image-raw-' + uuid.v4();
		var newPath = IMAGEFOLDER + newFile;

		// copy file
		fs.copy(oldPath, newPath, function (err) {
			if (err) console.log('copyRawFile err: ', err);
			callback(null, newFile);
		});
	},

	// add file to project
	addFileToProject : function (file, projectUuid) {
		Project
		.findOne({uuid : projectUuid})
		.exec(function (err, project) {
			project.files.push(file._id);
			project.markModified('files');
			project.save(function (err, doc) {
				if (err) console.log('File save error: ', err);
				console.log('File saved to project.');
			});
		});
	},

	// resize single image
	resizeImage : function (option, callback) {

		// basic options
		var width   	= parseInt(option.width) || null;
		var height  	= parseInt(option.height) || null;
		var quality 	= parseInt(option.quality) || 60;			// default quality 60

		console.log('width/height: ', width, height);

		// crop options
		option.crop = option.crop || {};
		var cropX   	= option.crop.x || 0;					// default crop is no crop, same dimensions and topleft
		var cropY   	= option.crop.y || 0;
		var cropW	= option.crop.w || width;
		var cropH 	= option.crop.h || height;

		// file options
		var path    	= option.file; 						// original file
		var newFile 	= 'image-' + uuid.v4();					// unique filename
		var newPath 	= IMAGEFOLDER + newFile;				// modified file

		gm.prototype.checkSize = function (action) {
			action()
			return this;
		}

		// do crunch magic
		gm(path)
		.resize(width)						// todo: if h/w is false, calc ratio
		.autoOrient()
		.crop(cropW, cropH, cropX, cropY)				// x, y is offset from top left corner
		.noProfile()							// todo: strip of all exif?
		.setFormat('JPEG')						// todo: watermark systemapic? or client?
		.quality(quality)
		.write(newPath, function (err) {
			if (err) console.log('22 resizeImage error: ', err);

			var result = {
				file   : newFile,
				height : height,
				width  : width,
				path : newPath
			}

			// return error and file
			callback(err, result);

		});
	},

	getIdentify : function (path, callback) {
		gm(path)
		.identify(function (err, exif){
			callback(err, exif);
		});
	},

	// cxxxxx

	serveFitPixelPerfection : function (req, res) {

		// set vars
		var quality    = req.query.quality;
		var imageId    = req.params[0]; 		// 'file-ccac0f45-ae95-41b9-8d57-0e64767ea9df'
		var fitW       = req.query.fitW;
		var fitH       = req.query.fitH;

		var newFile 	= 'image-' + uuid.v4();					// unique filename
		var newPath 	= IMAGEFOLDER + newFile;				// modified file

		var imagePath = '/data/images/' + imageId;

		pixels.getImageSize(imagePath, function (err, size) {

			if ( err || !size ) {
				console.log(err);
				return res.end();
			}

			var imgWidth = size.width;
			var imgHeight = size.height;

			if (imgWidth >= imgHeight) 	var imgLandscape = true;
			if (fitW >= fitH) 		var fitLandscape = true;

			if ( !fitLandscape && imgLandscape ) {

				// regn ut større bredde enn fitW...
				cropW = fitW * fitH/fitW;

				// Offset the X axis
				cropX = (cropW - fitW) / 2;
				cropY = 0;

			} else {

				cropW = fitW;

				// Find the image proportion
				var prop = imgWidth / cropW;

				// Find the image size with overflow
				cropH = imgHeight / prop;

				cropX = 0;

				// Offset the Y axis
				cropY = (cropH - fitH) / 2;
			}

			quality = 100;

			// do crunch magic
			gm(imagePath)
			.resize(cropW)							// the width of the image BEFORE cropping!
			.autoOrient()
			.crop(
				fitW,	// The actual width of the image
				fitH, 	// The actual height of the image
				cropX, 	// x, y is offset from top left corner
				cropY
			)
			.noProfile()							// todo: strip of all exif?
			.setFormat('JPEG')						// todo: watermark systemapic? or client?
			.quality(quality)
			.write(newPath, function (err) {
				if (err) console.log('px resizeImage error: ', err);

				res.sendfile(newPath, {maxAge : 10000000});
			});
		});

	},

	serveImagePixelPerfection : function (req, res) {

		// set vars
		var width      = req.query.width;
		var height     = req.query.height;
		var quality    = req.query.quality;
		var raw        = req.query.raw;
		var imageId    = req.params[0]; 		// 'file-ccac0f45-ae95-41b9-8d57-0e64767ea9df'
		var cropX      = req.query.cropx;
		var cropY      = req.query.cropy;
		var cropW      = req.query.cropw;
		var cropH      = req.query.croph;

		var imagePath = '/data/images/' + imageId;

		var options = {
			height: height,
			width : width,
			file : imagePath,
			crop : {
				x : cropX,
				y : cropY,
				h : cropH,
				w : cropW
			},
			quality : quality
		}

		// create image with dimensions
		pixels.resizeImage(options, function (err, result) {
			console.log('resized!!!', err, result);

			var path = result.path;

			res.sendFile(path, {maxAge : 10000000});
		});
	},

	// serve images without wasting a pixel
	servePixelPerfection : function (req, res) {

		// set vars
		var width      = req.query.width;
		var height     = req.query.height;
		var quality    = req.query.quality;
		var raw        = req.query.raw;
		var fileUuid   = req.params[0]; 		// 'file-ccac0f45-ae95-41b9-8d57-0e64767ea9df'
		var cropX      = req.query.cropx;
		var cropY      = req.query.cropy;
		var cropW      = req.query.cropw;
		var cropH      = req.query.croph;

		// return if invalid
		if (!req.query) return res.end();

		// if raw quality requested, return full image
		if (raw) return pixels.returnRawfile(req, res);

		// async waterfall (each fn passes results into next fn)
		var ops = [];

		// find file
		ops.push(function (callback) {

			File
			.findOne({uuid : fileUuid})
			.exec(function (err, file) {

				if (!file) return callback(err, {
					image : false,
					rawfile : false
				});

				// find image with right dimensions (exactly) // todo: % margins
				var image = _.find(file.data.image.crunched, function (i) {
					if (!i) return false;
					if (_.isObject(i.crop)) i.crop = {}; // prevent errors

					// check if crunched is same dimensions
					return 	parseInt(i.width)   == parseInt(width)    &&  	// width
						parseInt(i.height)  == parseInt(height)   &&  	// height
						parseInt(i.quality) == parseInt(quality)  && 	// quality
						parseInt(i.crop.x)  == parseInt(cropX)    && 	// crop
						parseInt(i.crop.y)  == parseInt(cropY)    && 	// crop
						parseInt(i.crop.h)  == parseInt(cropH)    && 	// crop
						parseInt(i.crop.w)  == parseInt(cropW); 	// crop

				});

				// return found image (if any)
				var rawfile = file.data.image.file;
				var vars = {
					rawfile : file.data.image.file,
					image : image
				}

				callback(err, vars);
			});
		});

		// create image if not found
		ops.push(function (vars, callback) {

			var image = vars.image;
			var rawfile = vars.rawfile;

			// return image if found
			if (image) {
				image.existing = true;
				return callback(null, image);
			}

			// if not found, create image
			var template = {
				file   : IMAGEFOLDER + rawfile,
				width  : width,
				height : height,
				quality : quality,
				crop : {
					x : cropX,
					y : cropY,
					h : cropH,
					w : cropW
				}
			}

			// create image with dimensions
			pixels.resizeImage(template, callback);

		});

		// image found or created
		ops.push(function (result, callback) {

			// add image to File object if it was created
			if (!result.existing) pixels.addImageToFile(result, fileUuid);

			// return result
			callback(null, result);

		});

		// run all async ops
		async.waterfall(ops, function (err, result) {
			// all done, serve file
			pixels.returnImage(req, res, result);
		});
	},

	// add crunched image to File object
	addImageToFile : function (result, fileUuid, callback) {
		File
		.findOne({uuid : fileUuid})
		.exec(function (err, file) {
			file.data.image.crunched.addToSet(result);
			file.markModified('data');
			file.save(function (err) {
				if (callback) callback(err, result);
			});
		});
	},

	// send image to client
	returnImage : function (req, res, imageFile) {
		// send file back to client, just need file path
		var path = IMAGEFOLDER + imageFile.file;
		res.sendfile(path, {maxAge : 10000000});	// cache age, 115 days.. cache not working?
	},

	returnRawfile : function (req, res) {

		// get file uuid
		var fileUuid = req.params[0];

		// get raw file
		File
		.findOne({uuid : fileUuid})
		.exec(function (err, file) {
			if (!file) return callback(err);

			// return raw file
			var imageFile = file.data.image;
			return pixels.returnImage(req, res, imageFile);
		});
		return;
	},

}<commit_msg>api + permissions<commit_after><|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule ToastAndroid
 */
'use strict';

module.exports = require('UnimplementedView');
<commit_msg>fix ToastAndroid<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule ToastAndroid
 */
'use strict';

var warning = require('warning');

var ToastAndroid = {

  show: function (
    message: string,
    duration: number
  ): void {
    warning(false, 'Cannot use ToastAndroid on iOS.');
  },

};

module.exports = ToastAndroid;
<|endoftext|>


<commit_before>import React from 'react';
import TypographyCSS from '../Typography/styles.css';
import ReactTextArea from 'react-textarea-autosize';
import styles from './styles.css';
const ENTER_KEY_ID = 13;

class TextArea extends React.Component {
    constructor(props) {
        super(props);
        this.handleKeyPress = this.handleKeyPress.bind(this);
    }
    handleKeyPress(event) {
        const emptyTextarea = !this.props.value || !this.props.value.length;
        const enterClick = event.which === ENTER_KEY_ID && !event.shiftKey;

        if (this.props.onSubmit && enterClick && emptyTextarea) {
            event.preventDefault();
        } else if (enterClick && this.props.onSubmit) {
            this.props.onSubmit();
            event.preventDefault();
        }
    }
    render() {
        const textType = this.props.textType || 'bodyTextNormal';

        return (
            <ReactTextArea
                className={`${styles.textarea} ${TypographyCSS[textType]}`}
                onChange={this.props.onChange}
                onKeyPress={this.handleKeyPress}
                placeholder={this.props.placeholder}
                value={this.props.value}
            />
        );
    }
}

TextArea.propTypes = {
    onChange: React.PropTypes.func,
    onClick: React.PropTypes.func,
    onSubmit: React.PropTypes.func,
    placeholder: React.PropTypes.string,
    showFullText: React.PropTypes.bool,
    textType: React.PropTypes.oneOf(Object.keys(TypographyCSS)),
    value: React.PropTypes.string
};

export default TextArea;
<commit_msg>Fixed TextArea classname<commit_after>import React from 'react';
import TypographyCSS from '../Typography/styles.css';
import ReactTextArea from 'react-textarea-autosize';
import classNames from 'classnames';
import styles from './styles.css';
const ENTER_KEY_ID = 13;

class TextArea extends React.Component {
    constructor(props) {
        super(props);
        this.handleKeyPress = this.handleKeyPress.bind(this);
    }
    handleKeyPress(event) {
        const emptyTextarea = !this.props.value || !this.props.value.length;
        const enterClick = event.which === ENTER_KEY_ID && !event.shiftKey;

        if (this.props.onSubmit && enterClick && emptyTextarea) {
            event.preventDefault();
        } else if (enterClick && this.props.onSubmit) {
            this.props.onSubmit();
            event.preventDefault();
        }
    }
    render() {
        const textType = this.props.textType || 'bodyTextNormal';

        return (
            <ReactTextArea
                className={classNames(styles.textarea, TypographyCSS[textType], {
                    [this.props.className]: this.props.className
                })}
                onChange={this.props.onChange}
                onKeyPress={this.handleKeyPress}
                placeholder={this.props.placeholder}
                value={this.props.value}
            />
        );
    }
}

TextArea.propTypes = {
    className: React.PropTypes.string,
    onChange: React.PropTypes.func,
    onClick: React.PropTypes.func,
    onSubmit: React.PropTypes.func,
    placeholder: React.PropTypes.string,
    showFullText: React.PropTypes.bool,
    textType: React.PropTypes.oneOf(Object.keys(TypographyCSS)),
    value: React.PropTypes.string
};

export default TextArea;
<|endoftext|>


<commit_before>// Main code running in every mathdown document (all shown via index.html).

"use strict";

// Prevent errors on IE.  Might not actually log.
function log() {
  try { console.log.apply(console, arguments); } catch (err) {}
}

// Send MathJax messages to log.
log("MJ:", MathJax.Message.Log());
var origFilterText = MathJax.Message.filterText;
MathJax.Message.filterText = function(text, n, msgType) {
  // Exclude non-informative "Processing/Typesetting math: 0% / 100%".
  if(msgType != "ProcessMath" && msgType != "TypesetMath") {
    log("MJ:", text, "[" + msgType + "]");
  }
  return origFilterText(text, n, msgType);
}

function locationQueryParams() {
  /* If more is needed, use https://github.com/medialize/URI.js */
  var queryParts = window.location.search.replace(/^\?/g, "").split("&");
  var params = {};
  for(var i = 0; i < queryParts.length; i++) {
    var keyval = queryParts[i].split("=");
    params[decodeURIComponent(keyval[0])] = decodeURIComponent(keyval[1] || "");
  }
  return params;
}

// Return an int in [0, 62).
var random62 = (window.crypto && window.crypto.getRandomValues) ?
    function() {
      var buf = new Uint8Array(1);
      var n;
      do {
        window.crypto.getRandomValues(buf);
        n = (buf[0] % 64);
      } while(n >= 62);
      return n;
    }
    :
    function() {
      // Math.random is pathetic on most browsers, though some initialize
      // with secure seed: http://stackoverflow.com/a/18507748/239657
      return Math.floor(Math.random() * 62);
    };

function randomBase62(length) {
  var base62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  var text = "";
  for(var i=0; i < length; i++)
    text += base62.charAt(random62());
  return text;
}

function newPad() {
  var doc = randomBase62(11);  // 62**11 ~= 2**65.
  window.open("?doc=" + doc, "_blank");
}

var doc = locationQueryParams()["doc"];
if(!doc) {
  window.location.search = "?doc=about";
}
var firepadsRef = new Firebase("https://mathdown.firebaseIO.com/firepads");
var firepadRef = firepadsRef.child(doc);
log("firebase ref:", firepadRef.toString());

// Stolen from CodeMirror/addon/edit/trailingspace.js.
CodeMirror.defineOption("showLeadingSpace", false, function(cm, val, prev) {
  if (prev == CodeMirror.Init) prev = false;
  if (prev && !val)
    cm.removeOverlay("leadingspace");
  else if (!prev && val)
    cm.addOverlay({
      token: function(stream) {
        if (stream.sol() && stream.eatSpace()) {
          return "leadingspace";
        } else {
          stream.skipToEnd();
          return null;
        }
      },
      name: "leadingspace"
    });
});

// To add vertical margin to headers, the .cm-header[N] classes must apply to
// <pre> and not the <span>.  Wrap the mode to achieve this.
CodeMirror.defineMode("gfm_header_line_classes", function(cmConfig, modeCfg) {
  modeCfg.name = "gfm";
  modeCfg.highlightFormatting = true;
  var mode = CodeMirror.getMode(cmConfig, modeCfg);
  var origToken = mode.token;
  // We use GFM mostly for URLs but don't want GitHub-specific formatting that
  // makes `0123456789` a link.  TODO: add mode option of gfm.js.
  var shaOrIssueRE = /^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40})|b^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+$/;
  mode.token = function(stream, state) {
    var classes = origToken(stream, state) || "";
    if (shaOrIssueRE.test(stream.current())) {
      classes = classes.replace(/(^| )link( |$)/, " ");
    }
    classes = classes.replace(/(^| )(header\S*)/g, "$1line-cm-$2");
    return /^\s*$/.test(classes) ? null : classes;
  }
  return mode;
});
var editor = CodeMirror.fromTextArea(document.getElementById("code"),
                                     {indentUnit: 4,
                                      lineNumbers: false,
                                      lineWrapping: true,
                                      mode: "gfm_header_line_classes",
                                      showLeadingSpace: true});

CodeMirror.hookMath(editor, MathJax);

var firepad =  Firepad.fromCodeMirror(firepadRef, editor);
firepad.on("ready", function() {
  if (firepad.isHistoryEmpty()) {
    firepad.setText(
      "# Untitled\n" +
        "Bookmark this page — the random-looking address is the only way to access this document!\n" +
        "Share the link to collaborate.\n" +
        "Edits are saved in real time.\n" +
        "\n" +
        "Use Markdown for document structure and $\\LaTeX$ math syntax (see **Help** link above).\n" +
        "To edit formulas just enter them with arrow keys.\n" +
        // HACK: Pad the editor with a few empty lines so it looks
        // more inviting to write in.
        "\n" +
        "\n"
    );
  } else {
    // Keeping cursor at start (hopefully) reduces CM redrawing as
    // syntax highlight and math rendering changes line heights.
    editor.setCursor({line: 0, ch: 0});
  }

  // CM's autofocus option doesn't work with Firepad - seems focused
  // but can't type.  Focusing here after Firepad init works.
  editor.focus();

  // Queuing this allows text to appear before math.
  MathJax.Hub.Queue(function() {
    editor.renderAllMath();
  });
});
<commit_msg>Indent soft-wrapped lines intelligently.  Mostly addresses #20. Uses indentation + list bullets. Not pixel-perfect – measured in "ex" of default variable-width font. Looks a bit off on Chrome, good on IE.<commit_after>// Main code running in every mathdown document (all shown via index.html).

"use strict";

// Prevent errors on IE.  Might not actually log.
function log() {
  try { console.log.apply(console, arguments); } catch (err) {}
}

// Send MathJax messages to log.
log("MJ:", MathJax.Message.Log());
var origFilterText = MathJax.Message.filterText;
MathJax.Message.filterText = function(text, n, msgType) {
  // Exclude non-informative "Processing/Typesetting math: 0% / 100%".
  if(msgType != "ProcessMath" && msgType != "TypesetMath") {
    log("MJ:", text, "[" + msgType + "]");
  }
  return origFilterText(text, n, msgType);
}

function locationQueryParams() {
  /* If more is needed, use https://github.com/medialize/URI.js */
  var queryParts = window.location.search.replace(/^\?/g, "").split("&");
  var params = {};
  for(var i = 0; i < queryParts.length; i++) {
    var keyval = queryParts[i].split("=");
    params[decodeURIComponent(keyval[0])] = decodeURIComponent(keyval[1] || "");
  }
  return params;
}

// Return an int in [0, 62).
var random62 = (window.crypto && window.crypto.getRandomValues) ?
    function() {
      var buf = new Uint8Array(1);
      var n;
      do {
        window.crypto.getRandomValues(buf);
        n = (buf[0] % 64);
      } while(n >= 62);
      return n;
    }
    :
    function() {
      // Math.random is pathetic on most browsers, though some initialize
      // with secure seed: http://stackoverflow.com/a/18507748/239657
      return Math.floor(Math.random() * 62);
    };

function randomBase62(length) {
  var base62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  var text = "";
  for(var i=0; i < length; i++)
    text += base62.charAt(random62());
  return text;
}

function newPad() {
  var doc = randomBase62(11);  // 62**11 ~= 2**65.
  window.open("?doc=" + doc, "_blank");
}

var doc = locationQueryParams()["doc"];
if(!doc) {
  window.location.search = "?doc=about";
}
var firepadsRef = new Firebase("https://mathdown.firebaseIO.com/firepads");
var firepadRef = firepadsRef.child(doc);
log("firebase ref:", firepadRef.toString());

// Stolen from CodeMirror/addon/edit/trailingspace.js.
CodeMirror.defineOption("showLeadingSpace", false, function(cm, val, prev) {
  if (prev == CodeMirror.Init) prev = false;
  if (prev && !val)
    cm.removeOverlay("leadingspace");
  else if (!prev && val)
    cm.addOverlay({
      token: function(stream) {
        if (stream.sol() && stream.eatSpace()) {
          return "leadingspace";
        } else {
          stream.skipToEnd();
          return null;
        }
      },
      name: "leadingspace"
    });
});

// To add vertical margin to headers, the .cm-header[N] classes must apply to
// <pre> and not the <span>.  Wrap the mode to achieve this.
CodeMirror.defineMode("gfm_header_line_classes", function(cmConfig, modeCfg) {
  modeCfg.name = "gfm";
  modeCfg.highlightFormatting = true;
  var mode = CodeMirror.getMode(cmConfig, modeCfg);
  var origToken = mode.token;
  // We use GFM mostly for URLs but don't want GitHub-specific formatting that
  // makes `0123456789` a link.  TODO: add mode option of gfm.js.
  var shaOrIssueRE = /^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40})|b^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+$/;
  mode.token = function(stream, state) {
    var classes = origToken(stream, state) || "";
    if (shaOrIssueRE.test(stream.current())) {
      classes = classes.replace(/(^| )link( |$)/, " ");
    }
    classes = classes.replace(/(^| )(header\S*)/g, "$1line-cm-$2");
    return /^\s*$/.test(classes) ? null : classes;
  }
  return mode;
});

var editor = CodeMirror.fromTextArea(document.getElementById("code"),
                                     {indentUnit: 4,
                                      lineNumbers: false,
                                      lineWrapping: true,
                                      mode: "gfm_header_line_classes",
                                      showLeadingSpace: true});

// Indent soft-wrapped lines.  Based on CodeMirror/demo/indentwrap.html.
var leadingSpaceAndListBullets = /^\s*([*+-]\s+|\d+\.\s+)?/;
editor.on("renderLine", function(cm, line, elt) {
  // Would like to measure real sizes of /leadingspace|formatting-list/ styled
  // spans, but can't do that without inserting them into the DOM.
  var leading = (leadingSpaceAndListBullets.exec(line.text) || [""])[0];
  var off = CodeMirror.countColumn(leading, leading.length, cm.getOption("tabSize"));
  // Using "ex" is a bit better than cm.defaultCharWidth() — it picks up
  // increased font if applied to whole line, i.e. in header lines
  // (not that it makes sense to indent headers).
  elt.style.textIndent = "-" + off + "ex";
  elt.style.paddingLeft = off + "ex";
});
editor.refresh();

CodeMirror.hookMath(editor, MathJax);

var firepad =  Firepad.fromCodeMirror(firepadRef, editor);
firepad.on("ready", function() {
  if (firepad.isHistoryEmpty()) {
    firepad.setText(
      "# Untitled\n" +
        "Bookmark this page — the random-looking address is the only way to access this document!\n" +
        "Share the link to collaborate.\n" +
        "Edits are saved in real time.\n" +
        "\n" +
        "Use Markdown for document structure and $\\LaTeX$ math syntax (see **Help** link above).\n" +
        "To edit formulas just enter them with arrow keys.\n" +
        // HACK: Pad the editor with a few empty lines so it looks
        // more inviting to write in.
        "\n" +
        "\n"
    );
  } else {
    // Keeping cursor at start (hopefully) reduces CM redrawing as
    // syntax highlight and math rendering changes line heights.
    editor.setCursor({line: 0, ch: 0});
  }

  // CM's autofocus option doesn't work with Firepad - seems focused
  // but can't type.  Focusing here after Firepad init works.
  editor.focus();

  // Queuing this allows text to appear before math.
  MathJax.Hub.Queue(function() {
    editor.renderAllMath();
  });
});
<|endoftext|>


<commit_before>(function(){
    'use strict';
    angular
        .module('SAKapp')
        .controller('LoggerController', ['$scope', '$http', '$log', '$q',  LoggerController]);

    function LoggerController ($scope, $http, $log, $q) {

        $scope.getLogs = function () {
            $scope.logs = "";
            $scope.errorMsg = "";

            $http({
                method: "GET",
                url: "/discover/pz-logger"
            }).then(function(result) {

                $http({
                    method: "GET",
                    url: "/logger/v1/messages",
                }).then(function successCallback( html ) {
                    $scope.logs = html.data;
                    /*angular.forEach($scope.logs, function(item){
                     console.log(item);
                     })*/
                }, function errorCallback(response){
                    console.log("fail");
                    $scope.errorMsg = "There was an issue with your request.  Please make sure ..."
                });

            });

        };

        $scope.postLog = function(){
            $scope.errorMsg = "";

            //var url = 'http://pz-logger.cf.piazzageo.io/log';
            var currentTime = moment().utc().toISOString();
            var logMessage = $scope.logMessage;
            var dataObj = {
                service: "log-tester",
                address: "128.1.2.3",
                time: currentTime,
                severity: "Info",
                message: logMessage
            }
            $http({
                method: "GET",
                url: "/discover/pz-logger"
            }).then(function(result) {

                $http.post(
                    "/logger/v1/messages",
                    dataObj
                ).then(function successCallback(res) {
                    $scope.message = res;
                    $scope.getLogs();
                    $scope.logMessage = null;

                    console.log("Success!");
                }, function errorCallback(res) {
                    console.log("fail");
                    $scope.successMsg = "There was a problem submitting the Log Message."
                    $scope.errorMsg = "Failure message: " + JSON.stringify({data: data});
                });
            })
        }
    }

})();<commit_msg>Remove discover call because we'll have to proxy it anyway<commit_after>(function(){
    'use strict';
    angular
        .module('SAKapp')
        .controller('LoggerController', ['$scope', '$http', '$log', '$q',  LoggerController]);

    function LoggerController ($scope, $http, $log, $q) {

        $scope.getLogs = function () {
            $scope.logs = "";
            $scope.errorMsg = "";

            //$http({
            //    method: "GET",
            //    url: "/discover/pz-logger"
            //}).then(function(result) {

                $http({
                    method: "GET",
                    url: "/logger/v1/messages",
                }).then(function successCallback( html ) {
                    $scope.logs = html.data;
                    /*angular.forEach($scope.logs, function(item){
                     console.log(item);
                     })*/
                }, function errorCallback(response){
                    console.log("fail");
                    $scope.errorMsg = "There was an issue with your request.  Please make sure ..."
                });

            //});

        };

        $scope.postLog = function(){
            $scope.errorMsg = "";

            //var url = 'http://pz-logger.cf.piazzageo.io/log';
            var currentTime = moment().utc().toISOString();
            var logMessage = $scope.logMessage;
            var dataObj = {
                service: "log-tester",
                address: "128.1.2.3",
                time: currentTime,
                severity: "Info",
                message: logMessage
            }
            $http({
                method: "GET",
                url: "/discover/pz-logger"
            }).then(function(result) {

                $http.post(
                    "/logger/v1/messages",
                    dataObj
                ).then(function successCallback(res) {
                    $scope.message = res;
                    $scope.getLogs();
                    $scope.logMessage = null;

                    console.log("Success!");
                }, function errorCallback(res) {
                    console.log("fail");
                    $scope.successMsg = "There was a problem submitting the Log Message."
                    $scope.errorMsg = "Failure message: " + JSON.stringify({data: data});
                });
            })
        }
    }

})();<|endoftext|>


<commit_before>var util = require('util');
var crypto = require('crypto');
var Scout = require('zetta-scout');

var AutoScout = module.exports = function() {
  var args = Array.prototype.slice.call(arguments);

  this.filter = args[0];
  this.constructor = args[1];
  this.params = args.slice(2);

  if (!(this instanceof AutoScout)) {
    var scout = new AutoScout();
    scout.filter = this.filter;
    scout.constructor = this.constructor;
    scout.params = this.params;

    return scout;
  }

  Scout.call(this);
};
util.inherits(AutoScout, Scout);

AutoScout.prototype._hash = function() {
  var stringifiedParams = JSON.stringify(this.params);
  var hash = crypto.createHash('sha1');
  hash.update(stringifiedParams);
  return hash.digest('hex');
};

AutoScout.prototype.init = function(cb) {
  var filter = typeof this.filter === 'string'
                 ? { type: this.filter }
                 : this.filter;
  var deviceHash = this._hash();
  filter.hash = deviceHash;
  var query = this.server.where(filter);

  var applyArgs = [].concat(this.params || []);
  applyArgs.unshift(this.constructor);

  var self = this;

  this.server.find(query, function(err, results) {
    if (err) {
      return cb(err);
    };

    if (!self._deviceInstance) {
      if (results.length) {
        var result = results[0];
        applyArgs.unshift(result);
        var device = self.provision.apply(self, applyArgs);
      } else {
        var device = self.discover.apply(self, applyArgs);
      }

      device.hash = deviceHash;
      device.save(function() {
        delete device.hash;
      });
    } else {
      var machine = self._deviceInstance;
      machine.hash = deviceHash;

      if (results.length) {
        machine.id = results[0].id; // must set id before machine_config runs
        machine.name = results[0].name;
      }

      self.server.registry.save(machine, function(err){
        delete machine.hash;
        self.server._jsDevices[machine.id] = machine;
        self.server.emit('deviceready', machine);
        if (results.length) {
          self.server._log.emit('log','scout', 'Device (' + machine.type + ') ' + machine.id + ' was provisioned from registry.' );
        } else {
          self.server._log.emit('log','scout', 'Device (' + machine.type + ') ' + machine.id + ' was discovered' );
        }
      });
    }

    cb();
  });
};
<commit_msg>Handle case with .provison returns null when the device has already been added once<commit_after>var util = require('util');
var crypto = require('crypto');
var Scout = require('zetta-scout');

var AutoScout = module.exports = function() {
  var args = Array.prototype.slice.call(arguments);

  this.filter = args[0];
  this.constructor = args[1];
  this.params = args.slice(2);

  if (!(this instanceof AutoScout)) {
    var scout = new AutoScout();
    scout.filter = this.filter;
    scout.constructor = this.constructor;
    scout.params = this.params;

    return scout;
  }

  Scout.call(this);
};
util.inherits(AutoScout, Scout);

AutoScout.prototype._hash = function() {
  var stringifiedParams = JSON.stringify(this.params);
  var hash = crypto.createHash('sha1');
  hash.update(stringifiedParams);
  return hash.digest('hex');
};

AutoScout.prototype.init = function(cb) {
  var filter = typeof this.filter === 'string'
                 ? { type: this.filter }
                 : this.filter;
  var deviceHash = this._hash();
  filter.hash = deviceHash;
  var query = this.server.where(filter);

  var applyArgs = [].concat(this.params || []);
  applyArgs.unshift(this.constructor);

  var self = this;

  this.server.find(query, function(err, results) {
    if (err) {
      return cb(err);
    };

    if (!self._deviceInstance) {
      if (results.length) {
        var result = results[0];
        applyArgs.unshift(result);
        var device = self.provision.apply(self, applyArgs);
      } else {
        var device = self.discover.apply(self, applyArgs);
      }

      // device is not always populated in the case of .provision
      if (device) {
        device.hash = deviceHash;
        device.save(function() {
          delete device.hash;
        });
      }
    } else {
      var machine = self._deviceInstance;
      machine.hash = deviceHash;

      if (results.length) {
        machine.id = results[0].id; // must set id before machine_config runs
        machine.name = results[0].name;
      }

      self.server.registry.save(machine, function(err){
        delete machine.hash;
        self.server._jsDevices[machine.id] = machine;
        self.server.emit('deviceready', machine);
        if (results.length) {
          self.server._log.emit('log','scout', 'Device (' + machine.type + ') ' + machine.id + ' was provisioned from registry.' );
        } else {
          self.server._log.emit('log','scout', 'Device (' + machine.type + ') ' + machine.id + ' was discovered' );
        }
      });
    }

    cb();
  });
};
<|endoftext|>


<commit_before>/*
 * Spreed WebRTC.
 * Copyright (C) 2013-2014 struktur AG
 *
 * This file is part of Spreed WebRTC.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
define(["jquery", "underscore", "sha", "webrtc.adapter"], function($, _, jsSHA) {

	var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem || null;
	if (!requestFileSystem) {
		console.warn("Browser support for filesystem API not found.");
	}

	// NOTE(longsleep): A single chunk is 60k - https://code.google.com/p/webrtc/issues/detail?id=2270
	// also Firefox does only support 16k maximum sents. See https://code.google.com/p/webrtc/issues/detail?id=2279.
	var fileChunkSize = webrtcDetectedBrowser === "chrome" ? 60000 : 16000;

	// Fake implementation for browsers which do not support FileSystem API.
	var FileWriterFake = function(owner) {
		this.owner = owner;
		this.file = null;
	};

	FileWriterFake.prototype.flush = function() {

		var l = this.owner.writeBuffer.length;
		this.owner.e.trigger("written", l, 0);
		if (this.owner.complete && !this.file) {
			// Sort.
			var writeBuffer = this.owner.writeBuffer.slice();
			writeBuffer.sort(function(a, b) {
				return a[0] - b[0];
			});
			// Strip sizes.
			var dataBuffer = writeBuffer.map(function(d) {
				// array contains [size, data]
				return d[1];
			});
			// Create.
			var data = new Blob(dataBuffer, {
				type: this.owner.info.type || "application/octet-stream"
			});
			this.file = {
				toURL: function() {
					return URL.createObjectURL(data);
				}
			}
			this.owner.file = this.file;
			this.owner.e.triggerHandler("complete", [this]);
		}

	};

	FileWriterFake.prototype.stop = function() {

		this.file = null;
		this.owner = null;

	};

	// FileSystem API writer.
	var FileWriterFileSystem = function(owner) {

		this.owner = owner;

		this.file = null;
		this.writing = false;
		this.written = 0;
		this.writing_written = 0;
		this.writer = null;

		this.create()

	};

	FileWriterFileSystem.prototype.supported = !! requestFileSystem;
	FileWriterFileSystem.prototype.filesystem = null;

	FileWriterFileSystem.prototype.create = function() {

		var generator = _.bind(function() {
			FileWriterFileSystem.prototype.filesystem.root.getFile(this.owner.id, {
				create: true,
				exclusive: true
			}, _.bind(function(fileEntry) {
				console.log("Generate file", this.owner, fileEntry);
				this.setup(fileEntry);
				this.flush();
			}, this), _.bind(function(e) {
				console.warn("Fileystem failed to create file.", e);
				this.owner.e.triggerHandler("failed", [e, this.owner]);
			}, this));
		}, this);
		if (!FileWriterFileSystem.prototype.filesystem) {
			this.requestFileSystem(null, _.bind(function(fs) {
				generator();
			}, this), _.bind(function(e) {
				this.owner.e.triggerHandler("error", [e, this.owner]);
			}, this));
		} else {
			generator();
		}

	};

	FileWriterFileSystem.prototype.requestFileSystem = function(size, cb, error) {

		if (!size) {
			// Default size.
			size = 5 * 1024 * 1024;
		}
		requestFileSystem(window.TEMPORARY, size, _.bind(function(fs) {
			// Success call.
			console.info("Opened file system.", fs.name)
			FileWriterFileSystem.prototype.filesystem = fs;
			if (cb) {
				cb(fs);
			}
		}, this), _.bind(function(e) {
			// Error call.
			console.warn("Failed to open file system.", e);
			if (error) {
				error(e);
			}
		}, this));

	};

	FileWriterFileSystem.prototype.setup = function(file) {
		this.file = file;
		this.file.createWriter(_.bind(function(fileWriter) {
			if (this.owner.stopped) {
				return;
			}
			//console.log("Writer created", fileWriter);
			fileWriter.onerror = _.bind(function(e) {
				console.warn("File write failed: " + e.toString());
				if (this.owner) {
					this.owner.e.triggerHandler("writeerror", [e, this]);
				}
			}, this);
			fileWriter.onwriteend = _.bind(function(e) {
				this.written += this.writing_written;
				this.writing_written = 0;
				this.writing = false;
				//console.log("Done file writing.", e, fileWriter.position, fileWriter.length);
				if (this.owner) {
					this.owner.e.trigger("written", this.written, this.owner.writeBuffer.length);
					this.flush();
				}
			}, this);
			//console.log("created writer for file", this.owner.info.size);
			this.writing = true;
			this.writer = fileWriter;
			this.written = 0;
			this.writing_written = 0;
			// Create fill with full size.
			fileWriter.truncate(this.owner.info.size || 0);
		}, this));
	};

	FileWriterFileSystem.prototype.flush = function() {

		if (this.writing || this.owner.stopped || !this.writer) {
			return;
		}

		var buffer = this.owner.writeBuffer;
		var length = buffer.length;

		if (length) {

			this.writing = true;
			//console.log("Flushing buffer to file writer", length);

			// Grab the current writeBuffer and empty it.
			var writeBuffer = buffer.splice(0, length);
			// Sort for bytes position.
			writeBuffer.sort(function(a, b) {
				return a[0] - b[0];
			});

			//console.log("In flush", writeBuffer.length);

			var next;
			var start = null;
			var pos;
			var d;
			var s;
			var tmp
			while (true) {

				// Grab next item.
				next = writeBuffer.shift();
				if (next) {
					s = next[0];
					d = next[1];
					if (start === null) {
						// Set write start bytes position and init write data.
						start = pos = s;
						tmp = [];
					}
				}

				if (pos !== s || !next) {

					// We have a gap or nothing more to write, seek and write the shit.
					//console.log("eyikes gap or finished", s, pos, next, this.writer.position, tmp.length);

					if (this.writer.position !== start) {
						//console.log("uhohu - seeking", this.writer.position, start);
						this.writer.seek(start);
					}

					// Write to file system.
					this.writing_written = tmp.length;
					this.writer.write(new Blob(tmp, {
						type: this.owner.info.type || "application/octet-stream"
					}));

					tmp = null;
					if (next) {
						// Add rest to buffer for a later flush.
						if (writeBuffer.length) {
							buffer.unshift.apply(buffer, writeBuffer);
						}
						buffer.unshift(next);
					}

					break;

				} else {

					// Add it to current write set.
					//console.log("coolio no gap", s, pos)
					tmp.push(d);
					pos += d.length;

				}

			}

		}

		if (this.owner.complete && !length) {
			this.owner.file = this.file;
			this.owner.e.triggerHandler("complete", [this]);
		}

	};

	FileWriterFileSystem.prototype.stop = function() {

		this.owner = null;
		this.writer = null;
		this.file = null;

	};

	// Create a reference to the supported writer.
	var FileWriter = FileWriterFileSystem.prototype.supported ? FileWriterFileSystem : FileWriterFake;

	// File wrapper.
	var File = function(id, file, info) {
		this.id = id;
		this.file = file;
		this.e = $({});
		this.scope = {}; // Additional data store.
		if (file) {
			this.info = {
				chunks: this.chunks = Math.ceil(file.size / fileChunkSize),
				size: file.size,
				name: file.name,
				type: file.type
			}
			this.complete = true;
		} else {
			this.info = info ? info : {};
			this.complete = false;
		}
		this.stopped = false;
		this.writer = null;
		this.writeBuffer = [];
	};

	File.prototype.getChunk = function(chunk_index, cb) {

		var start = fileChunkSize * chunk_index;
		// Avoid overseeks.
		var end = fileChunkSize * (chunk_index + 1);
		if (end > this.info.size) {
			end = this.info.size;
		}
		if (start >= end) {
			console.warn("Requested file chunk which is out of bound", chunk_index, start, end, this.info);
			return;
		}

		var reader = new FileReader();
		var blob = this.file.slice(start, end);
		reader.onload = function(event) {
			if (reader.readyState == FileReader.DONE) {
				cb(event.target.result);
			}
		};
		reader.readAsArrayBuffer(blob);

	};

	File.prototype.setChunk = function(chunk_index, byte_position, data) {

		if (this.stopped) {
			return;
		}

		this.writeBuffer.push([byte_position, data]);
		if (this.writer) {
			this.writer.flush();
		}

	};

	File.prototype.fileFinished = function() {

		if (!this.complete) {
			this.complete = true;
			this.flushToFilesystem();
		}

	};

	File.prototype.createWriter = function() {

		var writer = this.writer = new FileWriter(this);
		return writer;

	};

	File.prototype.flushToFilesystem = function(file) {
		if (this.stopped) {
			// Cannot do anything.
			return;
		}
		if (!this.writer) {
			this.createWriter(file);
		} else {
			this.writer.flush();
		}
	};

	File.prototype.toURL = function() {

		return this.file.toURL();

	};

	File.prototype.stop = function() {

		this.e.triggerHandler("stop", [this]);
		this.file = null;
		if (this.writer) {
			this.writer.stop();
			this.writer = null;
		}
		this.writeBuffer = [];
		this.stopped = true;

	};

	// fileData
	return ["randomGen", function(randomGen) {

		// NOTE(longsleep): Check if its a good idea to create this new every time when needed.
		var randomSecret = randomGen.random();
		//console.log("Random secret", [randomSecret]);;

		var fileCount = 0;
		var filenamePrefix = randomGen.id();
		var filesTable = {};

		// public API.
		var fileData = {
			generateFile: function(id, info) {
				id = filenamePrefix + id;
				var file = filesTable[id] = new File(id, null, info);
				file.createWriter();
				return file;
			},
			createFile: function(namespace, data) {
				var id = namespace + "_" + (fileCount++);
				var shaObj = new jsSHA(id, "TEXT");
				var token = shaObj.getHMAC(randomSecret, "TEXT", "SHA-384", "HEX");
				var file = filesTable[token] = new File(token, data);
				return file;
			},
			purgeFile: function(id) {
				var file = filesTable[id];
				if (file) {
					file.stop();
					delete filesTable[id];
				}
			},
			getFile: function(id) {
				return filesTable[id];
			}
		}

		return fileData;

	}];

});
<commit_msg>Cleanup temporary files on "stop".<commit_after>/*
 * Spreed WebRTC.
 * Copyright (C) 2013-2014 struktur AG
 *
 * This file is part of Spreed WebRTC.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
define(["jquery", "underscore", "sha", "webrtc.adapter"], function($, _, jsSHA) {

	var requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem || null;
	if (!requestFileSystem) {
		console.warn("Browser support for filesystem API not found.");
	}

	// NOTE(longsleep): A single chunk is 60k - https://code.google.com/p/webrtc/issues/detail?id=2270
	// also Firefox does only support 16k maximum sents. See https://code.google.com/p/webrtc/issues/detail?id=2279.
	var fileChunkSize = webrtcDetectedBrowser === "chrome" ? 60000 : 16000;

	// Fake implementation for browsers which do not support FileSystem API.
	var FileWriterFake = function(owner) {
		this.owner = owner;
		this.file = null;
	};

	FileWriterFake.prototype.flush = function() {

		var l = this.owner.writeBuffer.length;
		this.owner.e.trigger("written", l, 0);
		if (this.owner.complete && !this.file) {
			// Sort.
			var writeBuffer = this.owner.writeBuffer.slice();
			writeBuffer.sort(function(a, b) {
				return a[0] - b[0];
			});
			// Strip sizes.
			var dataBuffer = writeBuffer.map(function(d) {
				// array contains [size, data]
				return d[1];
			});
			// Create.
			var data = new Blob(dataBuffer, {
				type: this.owner.info.type || "application/octet-stream"
			});
			this.file = {
				toURL: function() {
					return URL.createObjectURL(data);
				}
			}
			this.owner.file = this.file;
			this.owner.e.triggerHandler("complete", [this]);
		}

	};

	FileWriterFake.prototype.stop = function() {

		this.file = null;
		this.owner = null;

	};

	// FileSystem API writer.
	var FileWriterFileSystem = function(owner) {

		this.owner = owner;

		this.file = null;
		this.writing = false;
		this.written = 0;
		this.writing_written = 0;
		this.writer = null;

		this.create()

	};

	FileWriterFileSystem.prototype.supported = !! requestFileSystem;
	FileWriterFileSystem.prototype.filesystem = null;

	FileWriterFileSystem.prototype.create = function() {

		var generator = _.bind(function() {
			FileWriterFileSystem.prototype.filesystem.root.getFile(this.owner.id, {
				create: true,
				exclusive: true
			}, _.bind(function(fileEntry) {
				console.log("Generate file", this.owner, fileEntry);
				this.setup(fileEntry);
				this.flush();
			}, this), _.bind(function(e) {
				console.warn("Fileystem failed to create file.", e);
				this.owner.e.triggerHandler("failed", [e, this.owner]);
			}, this));
		}, this);
		if (!FileWriterFileSystem.prototype.filesystem) {
			this.requestFileSystem(null, _.bind(function(fs) {
				generator();
			}, this), _.bind(function(e) {
				this.owner.e.triggerHandler("error", [e, this.owner]);
			}, this));
		} else {
			generator();
		}

	};

	FileWriterFileSystem.prototype.requestFileSystem = function(size, cb, error) {

		if (!size) {
			// Default size.
			size = 5 * 1024 * 1024;
		}
		requestFileSystem(window.TEMPORARY, size, _.bind(function(fs) {
			// Success call.
			console.info("Opened file system.", fs.name)
			FileWriterFileSystem.prototype.filesystem = fs;
			if (cb) {
				cb(fs);
			}
		}, this), _.bind(function(e) {
			// Error call.
			console.warn("Failed to open file system.", e);
			if (error) {
				error(e);
			}
		}, this));

	};

	FileWriterFileSystem.prototype.setup = function(file) {
		this.file = file;
		this.file.createWriter(_.bind(function(fileWriter) {
			if (this.owner.stopped) {
				return;
			}
			//console.log("Writer created", fileWriter);
			fileWriter.onerror = _.bind(function(e) {
				console.warn("File write failed: " + e.toString());
				if (this.owner) {
					this.owner.e.triggerHandler("writeerror", [e, this]);
				}
			}, this);
			fileWriter.onwriteend = _.bind(function(e) {
				this.written += this.writing_written;
				this.writing_written = 0;
				this.writing = false;
				//console.log("Done file writing.", e, fileWriter.position, fileWriter.length);
				if (this.owner) {
					this.owner.e.trigger("written", this.written, this.owner.writeBuffer.length);
					this.flush();
				}
			}, this);
			//console.log("created writer for file", this.owner.info.size);
			this.writing = true;
			this.writer = fileWriter;
			this.written = 0;
			this.writing_written = 0;
			// Create fill with full size.
			fileWriter.truncate(this.owner.info.size || 0);
		}, this));
	};

	FileWriterFileSystem.prototype.flush = function() {

		if (this.writing || this.owner.stopped || !this.writer) {
			return;
		}

		var buffer = this.owner.writeBuffer;
		var length = buffer.length;

		if (length) {

			this.writing = true;
			//console.log("Flushing buffer to file writer", length);

			// Grab the current writeBuffer and empty it.
			var writeBuffer = buffer.splice(0, length);
			// Sort for bytes position.
			writeBuffer.sort(function(a, b) {
				return a[0] - b[0];
			});

			//console.log("In flush", writeBuffer.length);

			var next;
			var start = null;
			var pos;
			var d;
			var s;
			var tmp
			while (true) {

				// Grab next item.
				next = writeBuffer.shift();
				if (next) {
					s = next[0];
					d = next[1];
					if (start === null) {
						// Set write start bytes position and init write data.
						start = pos = s;
						tmp = [];
					}
				}

				if (pos !== s || !next) {

					// We have a gap or nothing more to write, seek and write the shit.
					//console.log("eyikes gap or finished", s, pos, next, this.writer.position, tmp.length);

					if (this.writer.position !== start) {
						//console.log("uhohu - seeking", this.writer.position, start);
						this.writer.seek(start);
					}

					// Write to file system.
					this.writing_written = tmp.length;
					this.writer.write(new Blob(tmp, {
						type: this.owner.info.type || "application/octet-stream"
					}));

					tmp = null;
					if (next) {
						// Add rest to buffer for a later flush.
						if (writeBuffer.length) {
							buffer.unshift.apply(buffer, writeBuffer);
						}
						buffer.unshift(next);
					}

					break;

				} else {

					// Add it to current write set.
					//console.log("coolio no gap", s, pos)
					tmp.push(d);
					pos += d.length;

				}

			}

		}

		if (this.owner.complete && !length) {
			this.owner.file = this.file;
			this.owner.e.triggerHandler("complete", [this]);
		}

	};

	FileWriterFileSystem.prototype.stop = function() {

		this.owner = null;
		this.writer = null;
		this.file = null;

	};

	// Create a reference to the supported writer.
	var FileWriter = FileWriterFileSystem.prototype.supported ? FileWriterFileSystem : FileWriterFake;

	// File wrapper.
	var File = function(id, file, info) {
		this.id = id;
		this.file = file;
		this.e = $({});
		this.scope = {}; // Additional data store.
		if (file) {
			this.info = {
				chunks: this.chunks = Math.ceil(file.size / fileChunkSize),
				size: file.size,
				name: file.name,
				type: file.type
			}
			this.complete = true;
		} else {
			this.info = info ? info : {};
			this.complete = false;
		}
		this.stopped = false;
		this.writer = null;
		this.writeBuffer = [];
	};

	File.prototype.getChunk = function(chunk_index, cb) {

		var start = fileChunkSize * chunk_index;
		// Avoid overseeks.
		var end = fileChunkSize * (chunk_index + 1);
		if (end > this.info.size) {
			end = this.info.size;
		}
		if (start >= end) {
			console.warn("Requested file chunk which is out of bound", chunk_index, start, end, this.info);
			return;
		}

		var reader = new FileReader();
		var blob = this.file.slice(start, end);
		reader.onload = function(event) {
			if (reader.readyState == FileReader.DONE) {
				cb(event.target.result);
			}
		};
		reader.readAsArrayBuffer(blob);

	};

	File.prototype.setChunk = function(chunk_index, byte_position, data) {

		if (this.stopped) {
			return;
		}

		this.writeBuffer.push([byte_position, data]);
		if (this.writer) {
			this.writer.flush();
		}

	};

	File.prototype.fileFinished = function() {

		if (!this.complete) {
			this.complete = true;
			this.flushToFilesystem();
		}

	};

	File.prototype.createWriter = function() {

		var writer = this.writer = new FileWriter(this);
		return writer;

	};

	File.prototype.flushToFilesystem = function(file) {
		if (this.stopped) {
			// Cannot do anything.
			return;
		}
		if (!this.writer) {
			this.createWriter(file);
		} else {
			this.writer.flush();
		}
	};

	File.prototype.toURL = function() {

		return this.file.toURL();

	};

	File.prototype.stop = function() {

		this.e.triggerHandler("stop", [this]);
		if (this.file && this.file.remove) {
			var file = this.file;
			this.file.remove(_.bind(function() {
				console.log("File removed", file);
			}, this));
			this.file = null;
		}
		if (this.writer) {
			this.writer.stop();
			this.writer = null;
		}
		this.writeBuffer = [];
		this.stopped = true;

	};

	// fileData
	return ["randomGen", function(randomGen) {

		// NOTE(longsleep): Check if its a good idea to create this new every time when needed.
		var randomSecret = randomGen.random();
		//console.log("Random secret", [randomSecret]);;

		var fileCount = 0;
		var filenamePrefix = randomGen.id();
		var filesTable = {};

		// public API.
		var fileData = {
			generateFile: function(id, info) {
				id = filenamePrefix + id;
				var file = filesTable[id] = new File(id, null, info);
				file.createWriter();
				return file;
			},
			createFile: function(namespace, data) {
				var id = namespace + "_" + (fileCount++);
				var shaObj = new jsSHA(id, "TEXT");
				var token = shaObj.getHMAC(randomSecret, "TEXT", "SHA-384", "HEX");
				var file = filesTable[token] = new File(token, data);
				return file;
			},
			purgeFile: function(id) {
				var file = filesTable[id];
				if (file) {
					file.stop();
					delete filesTable[id];
				}
			},
			getFile: function(id) {
				return filesTable[id];
			}
		}

		return fileData;

	}];

});
<|endoftext|>