<commit_before>///import editor.js
///import core/browser.js
///import core/dom/dom.js
///import core/dom/dtd.js
///import core/dom/domUtils.js
///import core/dom/Range.js
/**
 * @class baidu.editor.dom.Selection    Selection类
 */
(function () {

    function getBoundaryInformation( range, start ) {
        var getIndex = domUtils.getNodeIndex;
        range = range.duplicate();
        range.collapse( start );
        var parent = range.parentElement();
        //如果节点里没有子节点，直接退出
        if ( !parent.hasChildNodes() ) {
            return  {container:parent, offset:0};
        }
        var siblings = parent.children,
            child,
            testRange = range.duplicate(),
            startIndex = 0, endIndex = siblings.length - 1, index = -1,
            distance;
        while ( startIndex <= endIndex ) {
            index = Math.floor( (startIndex + endIndex) / 2 );
            child = siblings[index];
            testRange.moveToElementText( child );
            var position = testRange.compareEndPoints( 'StartToStart', range );
            if ( position > 0 ) {
                endIndex = index - 1;
            } else if ( position < 0 ) {
                startIndex = index + 1;
            } else {
                //trace:1043
                return  {container:parent, offset:getIndex( child )};
            }
        }
        if ( index == -1 ) {
            testRange.moveToElementText( parent );
            testRange.setEndPoint( 'StartToStart', range );
            distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
            siblings = parent.childNodes;
            if ( !distance ) {
                child = siblings[siblings.length - 1];
                return  {container:child, offset:child.nodeValue.length};
            }

            var i = siblings.length;
            while ( distance > 0 ){
                distance -= siblings[ --i ].nodeValue.length;
            }
            return {container:siblings[i], offset:-distance};
        }
        testRange.collapse( position > 0 );
        testRange.setEndPoint( position > 0 ? 'StartToStart' : 'EndToStart', range );
        distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
        if ( !distance ) {
            return  dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName] ?
            {container:parent, offset:getIndex( child ) + (position > 0 ? 0 : 1)} :
            {container:child, offset:position > 0 ? 0 : child.childNodes.length}
        }
        while ( distance > 0 ) {
            try {
                var pre = child;
                child = child[position > 0 ? 'previousSibling' : 'nextSibling'];
                distance -= child.nodeValue.length;
            } catch ( e ) {
                return {container:parent, offset:getIndex( pre )};
            }
        }
        return  {container:child, offset:position > 0 ? -distance : child.nodeValue.length + distance}
    }

    /**
     * 将ieRange转换为Range对象
     * @param {Range}   ieRange    ieRange对象
     * @param {Range}   range      Range对象
     * @return  {Range}  range       返回转换后的Range对象
     */
    function transformIERangeToRange( ieRange, range ) {
        if ( ieRange.item ) {
            range.selectNode( ieRange.item( 0 ) );
        } else {
            var bi = getBoundaryInformation( ieRange, true );
            range.setStart( bi.container, bi.offset );
            if ( ieRange.compareEndPoints( 'StartToEnd', ieRange ) != 0 ) {
                bi = getBoundaryInformation( ieRange, false );
                range.setEnd( bi.container, bi.offset );
            }
        }
        return range;
    }

    /**
     * 获得ieRange
     * @param {Selection} sel    Selection对象
     * @return {ieRange}    得到ieRange
     */
    function _getIERange( sel ) {
        var ieRange;
        //ie下有可能报错
        try {
            ieRange = sel.getNative().createRange();
        } catch ( e ) {
            return null;
        }
        var el = ieRange.item ? ieRange.item( 0 ) : ieRange.parentElement();
        if ( ( el.ownerDocument || el ) === sel.document ) {
            return ieRange;
        }
        return null;
    }

    var Selection = dom.Selection = function ( doc ) {
        var me = this, iframe;
        me.document = doc;
        if ( browser.ie9below ) {
            iframe = domUtils.getWindow( doc ).frameElement;
            domUtils.on( iframe, 'beforedeactivate', function () {
                me._bakIERange = me.getIERange();
            } );
            domUtils.on( iframe, 'activate', function () {
                try {
                    if ( !_getIERange( me ) && me._bakIERange ) {
                        me._bakIERange.select();
                    }
                } catch ( ex ) {
                }
                me._bakIERange = null;
            } );
        }
        iframe = doc = null;
    };

    Selection.prototype = {
        /**
         * 获取原生seleciton对象
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.getNative
         * @return {Selection}    获得selection对象
         */
        getNative:function () {
            var doc = this.document;
            try {
                return !doc ? null : browser.ie9below ? doc.selection : domUtils.getWindow( doc ).getSelection();
            } catch ( e ) {
                return null;
            }
        },
        /**
         * 获得ieRange
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.getIERange
         * @return {ieRange}    返回ie原生的Range
         */
        getIERange:function () {
            var ieRange = _getIERange( this );
            if ( !ieRange ) {
                if ( this._bakIERange ) {
                    return this._bakIERange;
                }
            }
            return ieRange;
        },

        /**
         * 缓存当前选区的range和选区的开始节点
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.cache
         */
        cache:function () {
            this.clear();
            this._cachedRange = this.getRange();
            this._cachedStartElement = this.getStart();
            this._cachedStartElementPath = this.getStartElementPath();
        },

        getStartElementPath:function () {
            if ( this._cachedStartElementPath ) {
                return this._cachedStartElementPath;
            }
            var start = this.getStart();
            if ( start ) {
                return domUtils.findParents( start, true, null, true )
            }
            return [];
        },
        /**
         * 清空缓存
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.clear
         */
        clear:function () {
            this._cachedStartElementPath = this._cachedRange = this._cachedStartElement = null;
        },
        /**
         * 编辑器是否得到了选区
         */
        isFocus:function () {
            try {
                return browser.ie9below && _getIERange( this ) || !browser.ie9below && this.getNative().rangeCount ? true : false;
            } catch ( e ) {
                return false;
            }

        },
        /**
         * 获取选区对应的Range
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.getRange
         * @returns {baidu.editor.dom.Range}    得到Range对象
         */
        getRange:function () {
            var me = this;
            function optimze( range ) {
                var child = me.document.body.firstChild,
                    collapsed = range.collapsed;
                while ( child && child.firstChild ) {
                    range.setStart( child, 0 );
                    child = child.firstChild;
                }
                if ( !range.startContainer ) {
                    range.setStart( me.document.body, 0 )
                }
                if ( collapsed ) {
                    range.collapse( true );
                }
            }

            if ( me._cachedRange != null ) {
                return this._cachedRange;
            }
            var range = new baidu.editor.dom.Range( me.document );

            if ( browser.ie9below ) {
                var nativeRange = me.getIERange();
                if ( nativeRange ) {
                    //备份的_bakIERange可能已经实效了，dom树发生了变化比如从源码模式切回来，所以try一下，实效就放到body开始位置
                    try{
                        transformIERangeToRange( nativeRange, range );
                    }catch(e){
                        optimze( range );
                    }

                } else {
                    optimze( range );
                }
            } else {
                var sel = me.getNative();
                if ( sel && sel.rangeCount ) {
                    var firstRange = sel.getRangeAt( 0 );
                    var lastRange = sel.getRangeAt( sel.rangeCount - 1 );
                    range.setStart( firstRange.startContainer, firstRange.startOffset ).setEnd( lastRange.endContainer, lastRange.endOffset );
                    if ( range.collapsed && domUtils.isBody( range.startContainer ) && !range.startOffset ) {
                        optimze( range );
                    }
                } else {
                    //trace:1734 有可能已经不在dom树上了，标识的节点
                    if ( this._bakRange && domUtils.inDoc( this._bakRange.startContainer, this.document ) ){
                        return this._bakRange;
                    }
                    optimze( range );
                }
            }
            return this._bakRange = range;
        },

        /**
         * 获取开始元素，用于状态反射
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.getStart
         * @return {Element}     获得开始元素
         */
        getStart:function () {
            if ( this._cachedStartElement ) {
                return this._cachedStartElement;
            }
            var range = browser.ie9below ? this.getIERange() : this.getRange(),
                tmpRange,
                start, tmp, parent;
            if ( browser.ie9below ) {
                if ( !range ) {
                    //todo 给第一个值可能会有问题
                    return this.document.body.firstChild;
                }
                //control元素
                if ( range.item ){
                    return range.item( 0 );
                }
                tmpRange = range.duplicate();
                //修正ie下x[xx] 闭合后 x|xx
                tmpRange.text.length > 0 && tmpRange.moveStart( 'character', 1 );
                tmpRange.collapse( 1 );
                start = tmpRange.parentElement();
                parent = tmp = range.parentElement();
                while ( tmp = tmp.parentNode ) {
                    if ( tmp == start ) {
                        start = parent;
                        break;
                    }
                }
            } else {
                range.shrinkBoundary();
                start = range.startContainer;
                if ( start.nodeType == 1 && start.hasChildNodes() ){
                    start = start.childNodes[Math.min( start.childNodes.length - 1, range.startOffset )];
                }
                if ( start.nodeType == 3 ){
                    return start.parentNode;
                }
            }
            return start;
        },
        /**
         * 得到选区中的文本
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.getText
         * @return  {String}    选区中包含的文本
         */
        getText:function () {
            var nativeSel, nativeRange;
            if ( this.isFocus() && (nativeSel = this.getNative()) ) {
                nativeRange = browser.ie9below ? nativeSel.createRange() : nativeSel.getRangeAt( 0 );
                return browser.ie9below ? nativeRange.text : nativeRange.toString();
            }
            return '';
        },
        clearRange : function(){
            this.getNative()[browser.ie9below ? 'empty' : 'removeAllRanges']();
        }
    };
})();<commit_msg>修复在ie8下isFocus不准的问题<commit_after>///import editor.js
///import core/browser.js
///import core/dom/dom.js
///import core/dom/dtd.js
///import core/dom/domUtils.js
///import core/dom/Range.js
/**
 * @class baidu.editor.dom.Selection    Selection类
 */
(function () {

    function getBoundaryInformation( range, start ) {
        var getIndex = domUtils.getNodeIndex;
        range = range.duplicate();
        range.collapse( start );
        var parent = range.parentElement();
        //如果节点里没有子节点，直接退出
        if ( !parent.hasChildNodes() ) {
            return  {container:parent, offset:0};
        }
        var siblings = parent.children,
            child,
            testRange = range.duplicate(),
            startIndex = 0, endIndex = siblings.length - 1, index = -1,
            distance;
        while ( startIndex <= endIndex ) {
            index = Math.floor( (startIndex + endIndex) / 2 );
            child = siblings[index];
            testRange.moveToElementText( child );
            var position = testRange.compareEndPoints( 'StartToStart', range );
            if ( position > 0 ) {
                endIndex = index - 1;
            } else if ( position < 0 ) {
                startIndex = index + 1;
            } else {
                //trace:1043
                return  {container:parent, offset:getIndex( child )};
            }
        }
        if ( index == -1 ) {
            testRange.moveToElementText( parent );
            testRange.setEndPoint( 'StartToStart', range );
            distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
            siblings = parent.childNodes;
            if ( !distance ) {
                child = siblings[siblings.length - 1];
                return  {container:child, offset:child.nodeValue.length};
            }

            var i = siblings.length;
            while ( distance > 0 ){
                distance -= siblings[ --i ].nodeValue.length;
            }
            return {container:siblings[i], offset:-distance};
        }
        testRange.collapse( position > 0 );
        testRange.setEndPoint( position > 0 ? 'StartToStart' : 'EndToStart', range );
        distance = testRange.text.replace( /(\r\n|\r)/g, '\n' ).length;
        if ( !distance ) {
            return  dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName] ?
            {container:parent, offset:getIndex( child ) + (position > 0 ? 0 : 1)} :
            {container:child, offset:position > 0 ? 0 : child.childNodes.length}
        }
        while ( distance > 0 ) {
            try {
                var pre = child;
                child = child[position > 0 ? 'previousSibling' : 'nextSibling'];
                distance -= child.nodeValue.length;
            } catch ( e ) {
                return {container:parent, offset:getIndex( pre )};
            }
        }
        return  {container:child, offset:position > 0 ? -distance : child.nodeValue.length + distance}
    }

    /**
     * 将ieRange转换为Range对象
     * @param {Range}   ieRange    ieRange对象
     * @param {Range}   range      Range对象
     * @return  {Range}  range       返回转换后的Range对象
     */
    function transformIERangeToRange( ieRange, range ) {
        if ( ieRange.item ) {
            range.selectNode( ieRange.item( 0 ) );
        } else {
            var bi = getBoundaryInformation( ieRange, true );
            range.setStart( bi.container, bi.offset );
            if ( ieRange.compareEndPoints( 'StartToEnd', ieRange ) != 0 ) {
                bi = getBoundaryInformation( ieRange, false );
                range.setEnd( bi.container, bi.offset );
            }
        }
        return range;
    }

    /**
     * 获得ieRange
     * @param {Selection} sel    Selection对象
     * @return {ieRange}    得到ieRange
     */
    function _getIERange( sel ) {
        var ieRange;
        //ie下有可能报错
        try {
            ieRange = sel.getNative().createRange();
        } catch ( e ) {
            return null;
        }
        var el = ieRange.item ? ieRange.item( 0 ) : ieRange.parentElement();
        if ( ( el.ownerDocument || el ) === sel.document ) {
            return ieRange;
        }
        return null;
    }

    var Selection = dom.Selection = function ( doc ) {
        var me = this, iframe;
        me.document = doc;
        if ( browser.ie9below ) {
            iframe = domUtils.getWindow( doc ).frameElement;
            domUtils.on( iframe, 'beforedeactivate', function () {
                me._bakIERange = me.getIERange();
            } );
            domUtils.on( iframe, 'activate', function () {
                try {
                    if ( !_getIERange( me ) && me._bakIERange ) {
                        me._bakIERange.select();
                    }
                } catch ( ex ) {
                }
                me._bakIERange = null;
            } );
        }
        iframe = doc = null;
    };

    Selection.prototype = {
        rangeInBody : function(rng,txtRange){
            var node = browser.ie9below || txtRange ? rng.item ? rng.item() : rng.parentElement() : rng.startContainer;

            return node === this.document.body || domUtils.inDoc(node,this.document.body);
        },
        /**
         * 获取原生seleciton对象
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.getNative
         * @return {Selection}    获得selection对象
         */
        getNative:function () {
            var doc = this.document;
            try {
                return !doc ? null : browser.ie9below ? doc.selection : domUtils.getWindow( doc ).getSelection();
            } catch ( e ) {
                return null;
            }
        },
        /**
         * 获得ieRange
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.getIERange
         * @return {ieRange}    返回ie原生的Range
         */
        getIERange:function () {
            var ieRange = _getIERange( this );
            if ( !ieRange ) {
                if ( this._bakIERange ) {
                    return this._bakIERange;
                }
            }
            return ieRange;
        },

        /**
         * 缓存当前选区的range和选区的开始节点
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.cache
         */
        cache:function () {
            this.clear();
            this._cachedRange = this.getRange();
            this._cachedStartElement = this.getStart();
            this._cachedStartElementPath = this.getStartElementPath();
        },

        getStartElementPath:function () {
            if ( this._cachedStartElementPath ) {
                return this._cachedStartElementPath;
            }
            var start = this.getStart();
            if ( start ) {
                return domUtils.findParents( start, true, null, true )
            }
            return [];
        },
        /**
         * 清空缓存
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.clear
         */
        clear:function () {
            this._cachedStartElementPath = this._cachedRange = this._cachedStartElement = null;
        },
        /**
         * 编辑器是否得到了选区
         */
        isFocus:function () {
            try {
                if(browser.iebelow){
                    var nativeRange = _getIERange(this);
                    return nativeRange && this.rangeInBody(nativeRange);
                }else{
                    return !!this.getNative().rangeCount;
                }
            } catch ( e ) {
                return false;
            }

        },
        /**
         * 获取选区对应的Range
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.getRange
         * @returns {baidu.editor.dom.Range}    得到Range对象
         */
        getRange:function () {
            var me = this;
            function optimze( range ) {
                var child = me.document.body.firstChild,
                    collapsed = range.collapsed;
                while ( child && child.firstChild ) {
                    range.setStart( child, 0 );
                    child = child.firstChild;
                }
                if ( !range.startContainer ) {
                    range.setStart( me.document.body, 0 )
                }
                if ( collapsed ) {
                    range.collapse( true );
                }
            }

            if ( me._cachedRange != null ) {
                return this._cachedRange;
            }
            var range = new baidu.editor.dom.Range( me.document );

            if ( browser.ie9below ) {
                var nativeRange = me.getIERange();
                if ( nativeRange ) {
                    //备份的_bakIERange可能已经实效了，dom树发生了变化比如从源码模式切回来，所以try一下，实效就放到body开始位置
                    try{
                        transformIERangeToRange( nativeRange, range );
                    }catch(e){
                        optimze( range );
                    }

                } else {
                    optimze( range );
                }
            } else {
                var sel = me.getNative();
                if ( sel && sel.rangeCount ) {
                    var firstRange = sel.getRangeAt( 0 );
                    var lastRange = sel.getRangeAt( sel.rangeCount - 1 );
                    range.setStart( firstRange.startContainer, firstRange.startOffset ).setEnd( lastRange.endContainer, lastRange.endOffset );
                    if ( range.collapsed && domUtils.isBody( range.startContainer ) && !range.startOffset ) {
                        optimze( range );
                    }
                } else {
                    //trace:1734 有可能已经不在dom树上了，标识的节点
                    if ( this._bakRange && domUtils.inDoc( this._bakRange.startContainer, this.document ) ){
                        return this._bakRange;
                    }
                    optimze( range );
                }
            }
            return this._bakRange = range;
        },

        /**
         * 获取开始元素，用于状态反射
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.getStart
         * @return {Element}     获得开始元素
         */
        getStart:function () {
            if ( this._cachedStartElement ) {
                return this._cachedStartElement;
            }
            var range = browser.ie9below ? this.getIERange() : this.getRange(),
                tmpRange,
                start, tmp, parent;
            if ( browser.ie9below ) {
                if ( !range ) {
                    //todo 给第一个值可能会有问题
                    return this.document.body.firstChild;
                }
                //control元素
                if ( range.item ){
                    return range.item( 0 );
                }
                tmpRange = range.duplicate();
                //修正ie下x[xx] 闭合后 x|xx
                tmpRange.text.length > 0 && tmpRange.moveStart( 'character', 1 );
                tmpRange.collapse( 1 );
                start = tmpRange.parentElement();
                parent = tmp = range.parentElement();
                while ( tmp = tmp.parentNode ) {
                    if ( tmp == start ) {
                        start = parent;
                        break;
                    }
                }
            } else {
                range.shrinkBoundary();
                start = range.startContainer;
                if ( start.nodeType == 1 && start.hasChildNodes() ){
                    start = start.childNodes[Math.min( start.childNodes.length - 1, range.startOffset )];
                }
                if ( start.nodeType == 3 ){
                    return start.parentNode;
                }
            }
            return start;
        },
        /**
         * 得到选区中的文本
         * @public
         * @function
         * @name    baidu.editor.dom.Selection.getText
         * @return  {String}    选区中包含的文本
         */
        getText:function () {
            var nativeSel, nativeRange;
            if ( this.isFocus() && (nativeSel = this.getNative()) ) {
                nativeRange = browser.ie9below ? nativeSel.createRange() : nativeSel.getRangeAt( 0 );
                return browser.ie9below ? nativeRange.text : nativeRange.toString();
            }
            return '';
        },
        clearRange : function(){
            this.getNative()[browser.ie9below ? 'empty' : 'removeAllRanges']();
        }
    };
})();<|endoftext|>


<commit_before>/*
Scrape playlist pages and convert the track metadata within to json
*/
const fs = require('fs')
const cheerio = require('cheerio')
const Database = require('better-sqlite3')
const paths = require('./path.config')

const db = new Database('dumpfile.db', { fileMustExist: true })

function prepare(whereClause) {
  return db.prepare("SELECT * FROM dump WHERE " + whereClause)
}

function* getSongs() {
  const pages =
    prepare("path LIKE '%/playlists/%' AND content_type LIKE 'text/html%'")
  for (const page of pages.iterate()) {
    const playlistRe = /playlists\/([0-9]+)\//
    const match = page.path.match(playlistRe)
    if (!match) {
      continue
    }
    const playlistId = match[1]
    fs.writeFileSync(`./assets/${playlistId}.html`, page.data)

    const $ = cheerio.load(page.data)
    yield* $('#item_box_list .item_box').map((i, el) => {
      const item = $(el)
      const anchor = item.find('h5 > a')
      const imageUrl = item.find('img').attr('src')
      const imageIdRe = /\/([a-zA-Z0-9]+)[.](?:jpg|jpeg|png)/i

      return {
        id: item.find('.btn-play').data('id'),
        title: item.find('.work-item-info > h4').text().trim(),
        artist: anchor.text(),
        url: 'https://streetvoice.com' + anchor.attr('href'),
        imageUrl,
        imageId: imageUrl.match(imageIdRe)[1],
      }
    }).get()
  }
}

const songs = [...getSongs()]
fs.writeFileSync(paths.songsFile, JSON.stringify(songs, null, 2))

songs.forEach((song, i) => {
  console.log(`${i + 1}. ${song.title}`)
})
<commit_msg>Trim artist string when scraping from page<commit_after>/*
Scrape playlist pages and convert the track metadata within to json
*/
const fs = require('fs')
const cheerio = require('cheerio')
const Database = require('better-sqlite3')
const paths = require('./path.config')

const db = new Database('dumpfile.db', { fileMustExist: true })

function prepare(whereClause) {
  return db.prepare("SELECT * FROM dump WHERE " + whereClause)
}

function* getSongs() {
  const pages =
    prepare("path LIKE '%/playlists/%' AND content_type LIKE 'text/html%'")
  for (const page of pages.iterate()) {
    const playlistRe = /playlists\/([0-9]+)\//
    const match = page.path.match(playlistRe)
    if (!match) {
      continue
    }
    const playlistId = match[1]
    fs.writeFileSync(`./assets/${playlistId}.html`, page.data)

    const $ = cheerio.load(page.data)
    yield* $('#item_box_list .item_box').map((i, el) => {
      const item = $(el)
      const anchor = item.find('h5 > a')
      const imageUrl = item.find('img').attr('src')
      const imageIdRe = /\/([a-zA-Z0-9]+)[.](?:jpg|jpeg|png)/i

      return {
        id: item.find('.btn-play').data('id'),
        title: item.find('.work-item-info > h4').text().trim(),
        artist: anchor.text().trim(),
        url: 'https://streetvoice.com' + anchor.attr('href'),
        imageUrl,
        imageId: imageUrl.match(imageIdRe)[1],
      }
    }).get()
  }
}

const songs = [...getSongs()]
fs.writeFileSync(paths.songsFile, JSON.stringify(songs, null, 2))

songs.forEach((song, i) => {
  console.log(`${i + 1}. ${song.title}`)
})
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2010 Peter Parente based on Outfox
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**/
dojo.provide('info.mindtrove.JSonic');
dojo.require('dijit._Widget');
dojo.require("dojox.encoding.digests.MD5");

dojo.declare('info.mindtrove.JSonic', dijit._Widget, {
    jsonicURI: dojo.moduleUrl('', '../'),
    postMixInProperties: function() {
        // created audio channels
        this._channels = {};
        // cache of sounds and speech
        this._cache = new info.mindtrove.JSonicCache({jsonicURI : this.jsonicURI});
    },

    postCreate: function() {

    },

    /**
     * text, channel, name, cache
     */
    say: function(args) {
        args.method = '_say';
        this._getChannel(args.channel).push(args);
    },

    /**
     * url, channel, name, cache
     */
    play: function(args) {
        args.method = '_play';
        this._getChannel(args.channel).push(args);
    },

    stop: function() {
        var args = {method: '_stop'};
        this._getChannel(args.channel).push(args);
    },

    /**
     * name, value, channel, now
     */
    setProperty: function(args) {
        args.method = '_setProperty';
        this._getChannel(args.channel).push(args);
    },

    /**
     * name, channel, now
     */
    getProperty: function(args) {
        args.method = '_getProperty';
        args.deferred = new dojo.Deferred();
        this._getChannel(args.channel).push(args);
        return args.deferred;
    },

    /**
     * channel
     */
    reset: function(args) {
        args = args || {};
        args.method = '_reset';
        this._getChannel(args.channel).push(args);
    },

    getEngines: function() {
        return this._cache.getEngines();
    },

    getEngineInfo: function(name) {
        return this._cache.getEngineInfo(name);
    },

    addObserver: function(func, channel, actions) {
        var ob = this._getChannel(channel).addObserver(func, actions);
        return [ob, channel];
    },

    removeObserver: function(token) {
        this._getChannel(token[1]).removeObserver(token[0]);
    },

    _getChannel: function(id) {
        id = id || 'default';
        var ch = this._channels[id];
        if(ch === undefined) {
            ch = new info.mindtrove.JSonicChannel({
                id : id,
                cache : this._cache
            });
            this._channels[id] = ch;
        }
        return ch;
    }
});

dojo.declare('info.mindtrove.JSonicCache', dijit._Widget, {
    jsonicURI: null,
    postMixInProperties: function() {
        // speech engines and their details
        this._engineCache = null;
        // cache of speech utterances
        this._speechCache = {};
        // cache of speech filenames
        this._speechFiles = {};
        // cache of sound utterances
        this._soundCache = {};
        // cache of requests for speech rendering in progress
        this._speechRenderings = {};
        // determine extension to use
        var node = dojo.create('audio');
        if(node.canPlayType('audio/ogg')) {
            this._ext = '.ogg';
        } else if(node.canPlayType('audio/mpeg')) {
            this._ext = '.mp3';
        } else if(node.canPlayType('audio/aac')) {
            this._ext = '.m4a';
        } else if(node.canPlayType('audio/wav')) {
            this._ext = '.wav';
        } else {
            throw new Error('no known media supported');
        }
    },

    getEngines: function() {
        var request, def;
        if(this._engineCache) {
            def = new dojo.Deferred();
            var names = [];
            for(var key in this._engineCache) {
                names.push(key);
            }
            def.callback(names);
        } else {
            var request = {
                url : this.jsonicURI.uri+'engine',
                handleAs: 'json',
                load: dojo.hitch(this, function(response) {
                    this._engineCache = {};
                    dojo.forEach(response.result, 'this._engineCache[item] = null;', this);
                })
            };
            def = dojo.xhrGet(request);
        }
        return def;
    },

    getEngineInfo: function(name) {
        var request, def;
        if(this._engineCache[name]) {
            def = new dojo.Deferred();
            def.callback(this._engineCache[name]);
        } else {
            var request = {
                url : this.jsonicURI.uri+'engine/'+name,
                handleAs: 'json',
                load: dojo.hitch(this, function(response) {
                    this._engineCache[name] = response.result;
                })
            };
            def = dojo.xhrGet(request);
        }
        return def;
    },

    getSound: function(args) {
        var audioNode = this._soundCache[args.url];
        if(audioNode) {
            return dojo.clone(audioNode);
        } else {
            var node = dojo.create('audio');
            node.autobuffer = true;
            node.src = args.url+this._ext;
            if(args.cache) {
                this._soundCache[args.url] = node;
            }
            return node;
        }
    },

    _getSpeechCacheKey: function(text, props) {
        return text + '.' + props.voice + '.' + props.rate;
    },

    getSpeech: function(args, props) {
        var key = this._getSpeechCacheKey(args.text, props);
        var audioNode = this._speechCache[key];
        if(audioNode) {
            // clone existing audio node in cache
            return {name : 'audio', value : dojo.clone(audioNode)};
        }
        var resultDef = this._speechRenderings[key];
        if(resultDef) {
            // return deferred result for synth already in progress on server
            return {name : 'deferred', value : resultDef};
        }
        var response = this._speechFiles[key];
        if(response) {
            // build a new audio node for a known speech file url
            audioNode = this._onSpeechSynthed(args.text, props, response);
            return {name : 'audio', value : audioNode};
        }
        // synth on server
        var speechParams = {
            format : this._ext,
            utterances : {text : args.text},
            properties: props
        };
        resultDef = new dojo.Deferred();
        var request = {
            url : this.jsonicURI.uri+'synth',
            handleAs: 'json',
            postData : dojo.toJson(speechParams),
            load: dojo.hitch(this, '_onSpeechSynthed', resultDef, args,
                dojo.clone(props)),
            error: dojo.hitch(this, '_onSynthError', resultDef)
        };
        var xhrDef = dojo.xhrPost(request);
        this._speechRenderings[key] = resultDef;
        return {name : 'deferred', value : resultDef};
    },

    _onSynthError: function(resultDef, err, ioargs) {
        // clear request deferred
        var key = this._getSpeechCacheKey(args.text, props);
        delete this._speechRenderings[key];

        var response = dojo.fromJson(ioargs.xhr.responseText);
        resultDef.errback(response.description);
    },

    _onSpeechSynthed: function(resultDef, args, props, response) {
        var key = this._getSpeechCacheKey(args.text, props);
        delete this._speechRenderings[key];
        var node = dojo.create('audio');
        node.autobuffer = true;
        node.src = this.jsonicURI.uri+'files/'+response.result.text+this._ext;
        if(args.cache) {
            // cache the audio node
            this._speechCache[key] = node;
        } else {
            // cache the speech file url
            this._speechFiles[key] = response;
        }
        resultDef.callback(node);
    }
});

dojo.declare('info.mindtrove.JSonicChannel', dijit._Widget, {
    cache: null,
    postMixInProperties: function() {
        // current output, sound or speech
        this._kind = null;
        // optional name associated with current output
        this._name = null;
        // queue of commands to process
        this._queue = [];
        // busy processing a command or not
        this._busy = false;
        // observers of callbacks on this channel
        this._observers = [];
        // current channel properties
        this._properties = null;
        // current audio node using the channel
        this._audioNode = null;
        // callback tokens for the current audio node
        this._connects = [];
        // set default properties
        this._reset();
    },

    push: function(args) {
        if(args.method == '_play') {
            // pre-load sound
            args.audio = this.cache.getSound(args);
        } else if(args.method == '_say') {
            // pre-synth speech with any props ahead in the queue
            var props = dojo.clone(this._properties);
            var changes = dojo.forEach(this._queue, function(args) {
                if(args.method == '_setProperty') {
                    props[args.name] = args.value;
                }
            });
            args.audio = this.cache.getSpeech(args, props);
        }
        this._queue.push(args);
        this._pump();
    },

    addObserver: function(func, actions) {
        var ob = {func : func, actions : actions};
        this._observers.push(ob);
        return ob;
    },

    removeObserver: function(ob) {
        var obs = this._observers;
        for(var i=0; i < obs.length; i++) {
            if(obs[i] == ob) {
                this._observers = obs.slice(0, i).concat(obs.slice(i+1));
                break;
            }
        }
    },

    _pump: function() {
        while(!this._busy && this._queue.length) {
            var args = this._queue.shift();
            this._name = args.name;
            this[args.method](args);
        }
    },

    _playAudioNode: function(node) {
        this._audioNode = node;
        this._audioNode.volume = this._properties.volume;
        this._audioNode.loop = this._properties.loop;
        this._connects[0] = dojo.connect(node, 'play', this, '_onStart');
        this._connects[1] = dojo.connect(node, 'ended', this, '_onEnd');
        this._connects[2] = dojo.connect(node, 'error', this, '_onMediaError');
        this._audioNode.play();
    },

    _stopAudioNode: function() {
        this._audioNode.pause();
        dojo.forEach(this._connects, dojo.disconnect);
        this._connects = [];
        this._audioNode.loop = false;
        this._audioNode = null;
    },

    _say: function(args) {
        this._busy = true;
        this._kind = 'say';
        var obj = (args.audio) ? args.audio : this.cache.getSpeech(args);
        if(obj.name == 'audio') {
            this._playAudioNode(obj.value);
        } else if(obj.name == 'deferred') {
            obj.value.addCallback(dojo.hitch(this, '_playAudioNode'));
            obj.value.addErrback(dojo.hitch(this, '_onSynthError'));
        }
    },

    _play: function(args) {
        this._busy = true;
        this._kind = 'play';
        var node = (args.audio) ? args.audio : this.cache.getSound(args);
        this._playAudioNode(node);
    },

    _stop: function(args) {
        this._stopAudioNode();
        this._queue = [];
        this._kind = null;
        this._name = null;
        this._busy = false;
    },

    _setProperty: function(args) {
        this._properties[args.name] = args.value;
    },

    _getProperty: function(args) {
        var value = this._properties[args.name];
        args.deferred.callback(value);
    },

    _reset: function(args) {
        this._properties = {
            rate: 200,
            volume: 1.0,
            loop: false,
            engine : 'espeak',
            voice: 'en/en-r+f1'
        };
    },

    _notify: function(notice) {
        var obs = this._observers;
        for(var i=0; i < obs.length; i++) {
            var ob = obs[i];
            if(!ob.actions || dojo.indexOf(ob.actions, notice.action)) {
                try {
                    ob.func(notice);
                } catch(e) {
                    console.error(e.message);
                }

            }
        }
    },

    _onMediaError: function(event) {
        var notice = {
            action : 'error',
            channel : this.id,
            name : this._name,
            description: event.target.error
        };
        this._notify(notice);
        this._busy = false;
        this._name = null;
        this._pump();
    },

    _onSynthError: function(error) {
        var notice = {
            action : 'error',
            channel : this.id,
            name : this._name,
            description: error.message
        };
        this._notify(notice);
        this._busy = false;
        this._name = null;
        this._pump();
    },

    _onEnd: function(event) {
        var notice = {
            url : event.target.src,
            action : 'finished-'+this._kind,
            channel : this.id,
            name : this._name
        };
        this._stopAudioNode();
        this._notify(notice);
        this._busy = false;
        this._name = null;
        this._pump();
    },

    _onStart: function(event) {
        var notice = {
            url : event.target.src,
            action : 'started-'+this._kind,
            channel : this.id,
            name : this._name
        };
        this._notify(notice);
    }
});
<commit_msg>Started JS code comments<commit_after>/*
 * JSonic client-side API implemented using Dojo.
 *
 * :requires: Dojo 1.4, JSonic REST API on server
 * :copyright: Peter Parente 2010
 * :license: BSD
**/
dojo.provide('info.mindtrove.JSonic');
dojo.require('dijit._Widget');
dojo.require("dojox.encoding.digests.MD5");

/**
 * JSonic widget for application use.
 */
dojo.declare('info.mindtrove.JSonic', dijit._Widget, {
    // root of the JSonic server REST API, defaults to the current uri
    jsonicURI: dojo.moduleUrl('', '../'),
    postMixInProperties: function() {
        // created audio channels
        this._channels = {};
        // channel-shared cache of sounds and speech
        this._cache = new info.mindtrove.JSonicCache({
            jsonicURI : this.jsonicURI
        });
    },

    /**
     * Queues speech on a channel. The args parameter supports the following
     * name / value pairs.
     *
     * :param text:
     * :type text: string
     * :param channel:
     * :type channel: string
     * :param name:
     * :type name: string
     * :param cache:
     * :type cache: boolean
     */
    say: function(args) {
        args.method = '_say';
        this._getChannel(args.channel).push(args);
    },

    /**
     * Queues speech on a channel. The args parameter supports the following
     * name / value pairs.
     *
     * :param url:
     * :type url: string
     * :param channel:
     * :type channel: string
     * :param name:
     * :type name: string
     * :param cache:
     * :type cache: boolean
     */
    play: function(args) {
        args.method = '_play';
        this._getChannel(args.channel).push(args);
    },

    /**
     * Immediately stops output on a channel and clears the channel queue.
     * The args parameter supports the following name / value pairs.
     *
     * :param channel:
     * :type channel: string
     */
    stop: function(args) {
        var args = {method: '_stop'};
        this._getChannel(args.channel).push(args);
    },

    /**
     * Queues a change in channel property that affects all speech and sound
     * output following it in the queue. All channels support the following
     * properties:
     *
     * :rate: Speech rate in words per minute (default: 200)
     * :volume: Speech and sound volume beteween [0.0, 1.0] (default: 1.0)
     * :loop: Audio looping (default: false)
     * :engine: Speech engine to use (default: espeak)
     * :voice: Speech engine voice to use (default: en/en-r+f1)
     *
     * A channel may support additional speech properties if they are named
     * in the response from getEngineInfo.
     *
     * The args parameter supports the following name / value pairs.
     *
     * :param name:
     * :type name: string
     * :param value:
     * :type value: any
     * :param channel:
     * :type channel: string
     */
    setProperty: function(args) {
        args.method = '_setProperty';
        this._getChannel(args.channel).push(args);
    },

    /**
     * Queues a request for the value of a channel property. If the property is
     * unknown, undefined is returned.
     *
     * The args parameter supports the following name / value pairs.
     *
     * :param name:
     * :type name: string
     * :param channel:
     * :type channel: string
     * :return: Deferred with a callback to get the property value
     * :rtype: dojo.Deferred
     */
    getProperty: function(args) {
        args.method = '_getProperty';
        args.deferred = new dojo.Deferred();
        this._getChannel(args.channel).push(args);
        return args.deferred;
    },

    /**
     * Queues a reset of all channel property values to their default values.
     * The args parameter supports the following name / value pairs.
     *
     * :param channel:
     * :type channel: string
     */
    reset: function(args) {
        args = args || {};
        args.method = '_reset';
        this._getChannel(args.channel).push(args);
    },

    /**
     * Gets the list of speech engine names available on the server.
     *
     * :return: Deferred with a callback to get the engine list
     * :rtype: dojo.Deferred
     */
    getEngines: function() {
        return this._cache.getEngines();
    },

    /**
     * Gets a description of the properties supported by a single speech engine
     * available on the server.
     *
     * :return: Deferred with a callback to get the engine properties
     * :rtype: dojo.Deferred
     */
    getEngineInfo: function(name) {
        return this._cache.getEngineInfo(name);
    },

    /**
     * Adds an observer for channel events.
     *
     * :param func:
     * :type func: function
     * :param channel:
     * :type channel: string
     * :param actions:
     * :type actions: array
     * :return: Token to use when removing the observer
     * :rtype: object
     */
    addObserver: function(func, channel, actions) {
        var ob = this._getChannel(channel).addObserver(func, actions);
        return [ob, channel];
    },

    /**
     * Removes an observer of channel events.
     *
     * :param token:
     * :type token: object
     */
    removeObserver: function(token) {
        this._getChannel(token[1]).removeObserver(token[0]);
    },

    _getChannel: function(id) {
        id = id || 'default';
        var ch = this._channels[id];
        if(ch === undefined) {
            ch = new info.mindtrove.JSonicChannel({
                id : id,
                cache : this._cache
            });
            this._channels[id] = ch;
        }
        return ch;
    }
});

dojo.declare('info.mindtrove.JSonicCache', dijit._Widget, {
    jsonicURI: null,
    postMixInProperties: function() {
        // speech engines and their details
        this._engineCache = null;
        // cache of speech utterances
        this._speechCache = {};
        // cache of speech filenames
        this._speechFiles = {};
        // cache of sound utterances
        this._soundCache = {};
        // cache of requests for speech rendering in progress
        this._speechRenderings = {};
        // determine extension to use
        var node = dojo.create('audio');
        if(node.canPlayType('audio/ogg')) {
            this._ext = '.ogg';
        } else if(node.canPlayType('audio/mpeg')) {
            this._ext = '.mp3';
        } else if(node.canPlayType('audio/aac')) {
            this._ext = '.m4a';
        } else if(node.canPlayType('audio/wav')) {
            this._ext = '.wav';
        } else {
            throw new Error('no known media supported');
        }
    },

    getEngines: function() {
        var request, def;
        if(this._engineCache) {
            def = new dojo.Deferred();
            var names = [];
            for(var key in this._engineCache) {
                names.push(key);
            }
            def.callback(names);
        } else {
            var request = {
                url : this.jsonicURI.uri+'engine',
                handleAs: 'json',
                load: dojo.hitch(this, function(response) {
                    this._engineCache = {};
                    dojo.forEach(response.result, 'this._engineCache[item] = null;', this);
                })
            };
            def = dojo.xhrGet(request);
        }
        return def;
    },

    getEngineInfo: function(name) {
        var request, def;
        if(this._engineCache[name]) {
            def = new dojo.Deferred();
            def.callback(this._engineCache[name]);
        } else {
            var request = {
                url : this.jsonicURI.uri+'engine/'+name,
                handleAs: 'json',
                load: dojo.hitch(this, function(response) {
                    this._engineCache[name] = response.result;
                })
            };
            def = dojo.xhrGet(request);
        }
        return def;
    },

    getSound: function(args) {
        var audioNode = this._soundCache[args.url];
        if(audioNode) {
            return dojo.clone(audioNode);
        } else {
            var node = dojo.create('audio');
            node.autobuffer = true;
            node.src = args.url+this._ext;
            if(args.cache) {
                this._soundCache[args.url] = node;
            }
            return node;
        }
    },

    _getSpeechCacheKey: function(text, props) {
        return text + '.' + props.voice + '.' + props.rate;
    },

    getSpeech: function(args, props) {
        var key = this._getSpeechCacheKey(args.text, props);
        var audioNode = this._speechCache[key];
        if(audioNode) {
            // clone existing audio node in cache
            return {name : 'audio', value : dojo.clone(audioNode)};
        }
        var resultDef = this._speechRenderings[key];
        if(resultDef) {
            // return deferred result for synth already in progress on server
            return {name : 'deferred', value : resultDef};
        }
        var response = this._speechFiles[key];
        if(response) {
            // build a new audio node for a known speech file url
            audioNode = this._onSpeechSynthed(args.text, props, response);
            return {name : 'audio', value : audioNode};
        }
        // synth on server
        var speechParams = {
            format : this._ext,
            utterances : {text : args.text},
            properties: props
        };
        resultDef = new dojo.Deferred();
        var request = {
            url : this.jsonicURI.uri+'synth',
            handleAs: 'json',
            postData : dojo.toJson(speechParams),
            load: dojo.hitch(this, '_onSpeechSynthed', resultDef, args,
                dojo.clone(props)),
            error: dojo.hitch(this, '_onSynthError', resultDef)
        };
        var xhrDef = dojo.xhrPost(request);
        this._speechRenderings[key] = resultDef;
        return {name : 'deferred', value : resultDef};
    },

    _onSynthError: function(resultDef, err, ioargs) {
        // clear request deferred
        var key = this._getSpeechCacheKey(args.text, props);
        delete this._speechRenderings[key];

        var response = dojo.fromJson(ioargs.xhr.responseText);
        resultDef.errback(response.description);
    },

    _onSpeechSynthed: function(resultDef, args, props, response) {
        var key = this._getSpeechCacheKey(args.text, props);
        delete this._speechRenderings[key];
        var node = dojo.create('audio');
        node.autobuffer = true;
        node.src = this.jsonicURI.uri+'files/'+response.result.text+this._ext;
        if(args.cache) {
            // cache the audio node
            this._speechCache[key] = node;
        } else {
            // cache the speech file url
            this._speechFiles[key] = response;
        }
        resultDef.callback(node);
    }
});

dojo.declare('info.mindtrove.JSonicChannel', dijit._Widget, {
    cache: null,
    postMixInProperties: function() {
        // current output, sound or speech
        this._kind = null;
        // optional name associated with current output
        this._name = null;
        // queue of commands to process
        this._queue = [];
        // busy processing a command or not
        this._busy = false;
        // observers of callbacks on this channel
        this._observers = [];
        // current channel properties
        this._properties = null;
        // current audio node using the channel
        this._audioNode = null;
        // callback tokens for the current audio node
        this._connects = [];
        // set default properties
        this._reset();
    },

    push: function(args) {
        if(args.method == '_stop') {
            // stop immediately
            this._stop();
            return;
        } else if(args.method == '_play') {
            // pre-load sound
            args.audio = this.cache.getSound(args);
        } else if(args.method == '_say') {
            // pre-synth speech with any props ahead in the queue
            var props = dojo.clone(this._properties);
            var changes = dojo.forEach(this._queue, function(args) {
                if(args.method == '_setProperty') {
                    props[args.name] = args.value;
                }
            });
            args.audio = this.cache.getSpeech(args, props);
        }
        this._queue.push(args);
        this._pump();
    },

    addObserver: function(func, actions) {
        var ob = {func : func, actions : actions};
        this._observers.push(ob);
        return ob;
    },

    removeObserver: function(ob) {
        var obs = this._observers;
        for(var i=0; i < obs.length; i++) {
            if(obs[i] == ob) {
                this._observers = obs.slice(0, i).concat(obs.slice(i+1));
                break;
            }
        }
    },

    _pump: function() {
        while(!this._busy && this._queue.length) {
            var args = this._queue.shift();
            this._name = args.name;
            this[args.method](args);
        }
    },

    _playAudioNode: function(node) {
        this._audioNode = node;
        this._audioNode.volume = this._properties.volume;
        this._audioNode.loop = this._properties.loop;
        this._connects[0] = dojo.connect(node, 'play', this, '_onStart');
        this._connects[1] = dojo.connect(node, 'ended', this, '_onEnd');
        this._connects[2] = dojo.connect(node, 'error', this, '_onMediaError');
        this._audioNode.play();
    },

    _stopAudioNode: function() {
        this._audioNode.pause();
        dojo.forEach(this._connects, dojo.disconnect);
        this._connects = [];
        this._audioNode.loop = false;
        this._audioNode = null;
    },

    _say: function(args) {
        this._busy = true;
        this._kind = 'say';
        var obj = (args.audio) ? args.audio : this.cache.getSpeech(args);
        if(obj.name == 'audio') {
            this._playAudioNode(obj.value);
        } else if(obj.name == 'deferred') {
            obj.value.addCallback(dojo.hitch(this, '_playAudioNode'));
            obj.value.addErrback(dojo.hitch(this, '_onSynthError'));
        }
    },

    _play: function(args) {
        this._busy = true;
        this._kind = 'play';
        var node = (args.audio) ? args.audio : this.cache.getSound(args);
        this._playAudioNode(node);
    },

    _stop: function() {
        this._stopAudioNode();
        this._queue = [];
        this._kind = null;
        this._name = null;
        this._busy = false;
    },

    _setProperty: function(args) {
        this._properties[args.name] = args.value;
    },

    _getProperty: function(args) {
        var value = this._properties[args.name];
        args.deferred.callback(value);
    },

    _reset: function(args) {
        this._properties = {
            rate: 200,
            volume: 1.0,
            loop: false,
            engine : 'espeak',
            voice: 'en/en-r+f1'
        };
    },

    _notify: function(notice) {
        var obs = this._observers;
        for(var i=0; i < obs.length; i++) {
            var ob = obs[i];
            if(!ob.actions || dojo.indexOf(ob.actions, notice.action)) {
                try {
                    ob.func(notice);
                } catch(e) {
                    console.error(e.message);
                }

            }
        }
    },

    _onMediaError: function(event) {
        var notice = {
            action : 'error',
            channel : this.id,
            name : this._name,
            description: event.target.error
        };
        this._notify(notice);
        this._busy = false;
        this._name = null;
        this._pump();
    },

    _onSynthError: function(error) {
        var notice = {
            action : 'error',
            channel : this.id,
            name : this._name,
            description: error.message
        };
        this._notify(notice);
        this._busy = false;
        this._name = null;
        this._pump();
    },

    _onEnd: function(event) {
        var notice = {
            url : event.target.src,
            action : 'finished-'+this._kind,
            channel : this.id,
            name : this._name
        };
        this._stopAudioNode();
        this._notify(notice);
        this._busy = false;
        this._name = null;
        this._pump();
    },

    _onStart: function(event) {
        var notice = {
            url : event.target.src,
            action : 'started-'+this._kind,
            channel : this.id,
            name : this._name
        };
        this._notify(notice);
    }
});
<|endoftext|>


<commit_before>import Ember from 'ember';

const {
  Mixin,
  on,
  run: {
    bind
  }
} = Ember;

export default Mixin.create({
  recognizers: [
    ['tap'],
    ['press'],
    ['pan'],
    ['swipe'],
    ['pinch']
  ],

  setup: on('didInsertElement', function() {
    if (!this.get('recognizers')) {
      return;
    }

    this.$().hammer();

    this.get('recognizers').forEach((item) => {
      if (item[1]) {
        this.$().data('hammer').get(item[0]).set(item[1]);
      }

      if (typeof this[item[0]] === 'function') {
        this.$().hammer().on(item[0], bind(this, item[0]));
      }
    });
  }),

  teardown: on('willDestroyElement', function() {
    if (!this.get('recognizers')) {
      return;
    }

    this.get('recognizers').forEach((item) => {
      if (typeof this[item[0]] === 'function') {
        this.$().hammer().off(item[0], bind(this, item[0]));
      }
    });
  })
});
<commit_msg>remove default recognizers<commit_after>import Ember from 'ember';

const {
  Mixin,
  on,
  run: {
    bind
  }
} = Ember;

export default Mixin.create({
  setup: on('didInsertElement', function() {
    if (!this.get('recognizers')) {
      return;
    }

    this.$().hammer();

    this.get('recognizers').forEach((item) => {
      if (item[1]) {
        this.$().data('hammer').get(item[0]).set(item[1]);
      }

      if (typeof this[item[0]] === 'function') {
        this.$().hammer().on(item[0], bind(this, item[0]));
      }
    });
  }),

  teardown: on('willDestroyElement', function() {
    if (!this.get('recognizers')) {
      return;
    }

    this.get('recognizers').forEach((item) => {
      if (typeof this[item[0]] === 'function') {
        this.$().hammer().off(item[0], bind(this, item[0]));
      }
    });
  })
});
<|endoftext|>


<commit_before>(function () {
    "use strict";
    var FS = require('fs');
    var events = require('events');
    var util = require('util');
    var JsonUtils = require("./lib/utils");
    var DBParentData = require("./lib/DBParentData");
    var DatabaseError = require("./lib/Errors").DatabaseError;
    var DataError = require("./lib/Errors").DataError;
    var mkdirp = require('mkdirp');
    var path = require('path');

    var JsonDB = function (filename, saveOnPush, humanReadable) {

        this.filename = filename;

        if (!JsonUtils.strEndWith(filename, ".json")) {
            this.filename += ".json";
        }
        var self = this;
        this.loaded = false;
        this.data = {};
        if (!FS.existsSync(this.filename)) {
            var dirname = path.dirname(this.filename);
            mkdirp.sync(dirname);
            self.save(true);
            self.loaded = true;
            util.log("[JsonDB] DataBase " + self.filename + " created.");
        }
        this.saveOnPush = saveOnPush || true;
        if (humanReadable) {
            this.humanReadable = humanReadable;
        }
        else {
            this.humanReadable = false;
        }

        return this;
    };
    JsonDB.prototype._processDataPath = function (dataPath) {
        if (dataPath === undefined || !dataPath.trim()) {
            throw new DataError("The Data Path can't be empty", 6);
        }
        if (dataPath == "/") {
            return [];
        }
        var path = dataPath.split("/");
        path.shift();
        return path;
    };

    JsonDB.prototype._getParentData = function (dataPath, create) {
        var path = this._processDataPath(dataPath);
        var last = path.pop();
        return new DBParentData(last, this._getData(path, create), this, dataPath);
    };
    /**
     * Get the deta stored in the data base
     * @param dataPath path leading to the data
     * @returns {*}
     */
    JsonDB.prototype.getData = function (dataPath) {
        var path = this._processDataPath(dataPath);
        return this._getData(path);
    };

    JsonDB.prototype._getData = function (dataPath, create) {

        this.load();

        create = create || false;
        dataPath = JsonUtils.removeTrailingSlash(dataPath);

        function recursiveProcessDataPath(data, index) {

            var property = dataPath[index];

            /**
             * Find the wanted Data or create it.
             */
            function findData(isArray) {
                isArray = isArray || false;
                if (data.hasOwnProperty(property)) {
                    data = data[property];
                } else if (create) {
                    if (isArray) {
                        data[property] = [];
                    } else {
                        data[property] = {};
                    }
                    data = data[property];
                } else {
                    throw new DataError("Can't find dataPath: /" + dataPath.join("/") + ". Stopped at " + property, 5);
                }
            }

            var arrayInfo = JsonUtils.processArray(property);
            if (arrayInfo) {
                property = arrayInfo.property;
                findData(true);
                if (!Array.isArray(data)) {
                    throw new DataError("DataPath: /" + dataPath.join("/") + ". " + property + " is not an array.", 11);
                }
                if (data.hasOwnProperty(arrayInfo.index)) {
                    data = data[arrayInfo.index];
                } else if (create) {
                    data[arrayInfo.index] = {};
                    data = data[arrayInfo.index];
                } else {
                    throw new DataError("DataPath: /" + dataPath.join("/") + ". Can't find index " + arrayInfo.index + " in array " + property, 10);
                }
            } else {
                findData();
            }

            if (dataPath.length == ++index) {
                return data;
            }
            return recursiveProcessDataPath(data, index);
        }

        if (dataPath.length === 0) {
            return this.data;
        }

        return recursiveProcessDataPath(this.data, 0);

    };

    /**
     * Pushing data into the database
     * @param dataPath path leading to the data
     * @param data data to push
     * @param override overriding or not the data, if not, it will merge them
     */
    JsonDB.prototype.push = function (dataPath, data, override) {
        override = override === undefined ? true : override;

        dataPath = JsonUtils.removeTrailingSlash(dataPath);
        var dbData = this._getParentData(dataPath, true);
        if (!dbData) {
            throw new Error("Data not found");
        }
        var toSet = data;
        if (!override) {
            if (Array.isArray(data)) {
                var storedData = dbData.getData();
                if (storedData === undefined) {
                    storedData = [];
                } else if (!Array.isArray(storedData)) {
                    throw new DataError("Can't merge another type of data with an Array", 3);
                }
                toSet = storedData.concat(data);
            } else if (data === Object(data)) {
                if (Array.isArray(dbData.getData())) {
                    throw  new DataError("Can't merge an Array with an Object", 4);
                }
                toSet = JsonUtils.mergeObject(dbData.getData(), data);
            }
        }
        dbData.setData(toSet);

        if (this.saveOnPush) {
            this.save();
        }
    };
    /**
     * Delete the data
     * @param dataPath path leading to the data
     */
    JsonDB.prototype.delete = function (dataPath) {
        dataPath = JsonUtils.removeTrailingSlash(dataPath);
        var dbData = this._getParentData(dataPath, true);
        if (!dbData) {
            return;
        }
        dbData.delete();
    };
    /**
     * Reload the database from the file
     * @returns {*}
     */
    JsonDB.prototype.reload = function () {
        this.loaded = false;
        return this.load();
    };
    /**
     * Manually load the database
     * It is automatically called when the first getData is done
     */
    JsonDB.prototype.load = function () {
        if (this.loaded) {
            return;
        }
        try {
            var data = FS.readFileSync(this.filename, 'utf8');
            this.data = JSON.parse(data);
            this.loaded = true;
            util.log("[JsonDB] DataBase " + this.filename + " loaded.");
        } catch (err) {
            var error = new DatabaseError("Can't Load Database", 1, err);
            error.inner = err;
            throw error;
        }
    };
    /**
     * Manually save the database
     * By default you can't save the database if it's not loaded
     * @param force force the save of the database
     */
    JsonDB.prototype.save = function (force) {
        force = force || false;
        if (!force && !this.loaded) {
            throw new DatabaseError("DataBase not loaded. Can't write", 7);
        }
        var data = "";
        try {
            if (this.humanReadable) {
                data = JSON.stringify(this.data, null, 4);
            }
            else {
                data = JSON.stringify(this.data);
            }
            FS.writeFileSync(this.filename, data, 'utf8');
        } catch (err) {
            var error = new DatabaseError("Can't save the database", 2, err);
            error.inner = err;
            throw error;
        }

    };

    module.exports = JsonDB;
})();<commit_msg>Fixed save option bug<commit_after>(function () {
    "use strict";
    var FS = require('fs');
    var events = require('events');
    var util = require('util');
    var JsonUtils = require("./lib/utils");
    var DBParentData = require("./lib/DBParentData");
    var DatabaseError = require("./lib/Errors").DatabaseError;
    var DataError = require("./lib/Errors").DataError;
    var mkdirp = require('mkdirp');
    var path = require('path');

    var JsonDB = function (filename, saveOnPush, humanReadable) {

        this.filename = filename;

        if (!JsonUtils.strEndWith(filename, ".json")) {
            this.filename += ".json";
        }
        var self = this;
        this.loaded = false;
        this.data = {};
        if (!FS.existsSync(this.filename)) {
            var dirname = path.dirname(this.filename);
            mkdirp.sync(dirname);
            self.save(true);
            self.loaded = true;
            util.log("[JsonDB] DataBase " + self.filename + " created.");
        }
        this.saveOnPush = ( typeof( saveOnPush ) == "boolean" ) ? saveOnPush : true;
        if (humanReadable) {
            this.humanReadable = humanReadable;
        }
        else {
            this.humanReadable = false;
        }

        return this;
    };
    JsonDB.prototype._processDataPath = function (dataPath) {
        if (dataPath === undefined || !dataPath.trim()) {
            throw new DataError("The Data Path can't be empty", 6);
        }
        if (dataPath == "/") {
            return [];
        }
        var path = dataPath.split("/");
        path.shift();
        return path;
    };

    JsonDB.prototype._getParentData = function (dataPath, create) {
        var path = this._processDataPath(dataPath);
        var last = path.pop();
        return new DBParentData(last, this._getData(path, create), this, dataPath);
    };
    /**
     * Get the deta stored in the data base
     * @param dataPath path leading to the data
     * @returns {*}
     */
    JsonDB.prototype.getData = function (dataPath) {
        var path = this._processDataPath(dataPath);
        return this._getData(path);
    };

    JsonDB.prototype._getData = function (dataPath, create) {

        this.load();

        create = create || false;
        dataPath = JsonUtils.removeTrailingSlash(dataPath);

        function recursiveProcessDataPath(data, index) {

            var property = dataPath[index];

            /**
             * Find the wanted Data or create it.
             */
            function findData(isArray) {
                isArray = isArray || false;
                if (data.hasOwnProperty(property)) {
                    data = data[property];
                } else if (create) {
                    if (isArray) {
                        data[property] = [];
                    } else {
                        data[property] = {};
                    }
                    data = data[property];
                } else {
                    throw new DataError("Can't find dataPath: /" + dataPath.join("/") + ". Stopped at " + property, 5);
                }
            }

            var arrayInfo = JsonUtils.processArray(property);
            if (arrayInfo) {
                property = arrayInfo.property;
                findData(true);
                if (!Array.isArray(data)) {
                    throw new DataError("DataPath: /" + dataPath.join("/") + ". " + property + " is not an array.", 11);
                }
                if (data.hasOwnProperty(arrayInfo.index)) {
                    data = data[arrayInfo.index];
                } else if (create) {
                    data[arrayInfo.index] = {};
                    data = data[arrayInfo.index];
                } else {
                    throw new DataError("DataPath: /" + dataPath.join("/") + ". Can't find index " + arrayInfo.index + " in array " + property, 10);
                }
            } else {
                findData();
            }

            if (dataPath.length == ++index) {
                return data;
            }
            return recursiveProcessDataPath(data, index);
        }

        if (dataPath.length === 0) {
            return this.data;
        }

        return recursiveProcessDataPath(this.data, 0);

    };

    /**
     * Pushing data into the database
     * @param dataPath path leading to the data
     * @param data data to push
     * @param override overriding or not the data, if not, it will merge them
     */
    JsonDB.prototype.push = function (dataPath, data, override) {
        override = override === undefined ? true : override;

        dataPath = JsonUtils.removeTrailingSlash(dataPath);
        var dbData = this._getParentData(dataPath, true);
        if (!dbData) {
            throw new Error("Data not found");
        }
        var toSet = data;
        if (!override) {
            if (Array.isArray(data)) {
                var storedData = dbData.getData();
                if (storedData === undefined) {
                    storedData = [];
                } else if (!Array.isArray(storedData)) {
                    throw new DataError("Can't merge another type of data with an Array", 3);
                }
                toSet = storedData.concat(data);
            } else if (data === Object(data)) {
                if (Array.isArray(dbData.getData())) {
                    throw  new DataError("Can't merge an Array with an Object", 4);
                }
                toSet = JsonUtils.mergeObject(dbData.getData(), data);
            }
        }
        dbData.setData(toSet);

        if (this.saveOnPush) {
            this.save();
        }
    };
    /**
     * Delete the data
     * @param dataPath path leading to the data
     */
    JsonDB.prototype.delete = function (dataPath) {
        dataPath = JsonUtils.removeTrailingSlash(dataPath);
        var dbData = this._getParentData(dataPath, true);
        if (!dbData) {
            return;
        }
        dbData.delete();
    };
    /**
     * Reload the database from the file
     * @returns {*}
     */
    JsonDB.prototype.reload = function () {
        this.loaded = false;
        return this.load();
    };
    /**
     * Manually load the database
     * It is automatically called when the first getData is done
     */
    JsonDB.prototype.load = function () {
        if (this.loaded) {
            return;
        }
        try {
            var data = FS.readFileSync(this.filename, 'utf8');
            this.data = JSON.parse(data);
            this.loaded = true;
            util.log("[JsonDB] DataBase " + this.filename + " loaded.");
        } catch (err) {
            var error = new DatabaseError("Can't Load Database", 1, err);
            error.inner = err;
            throw error;
        }
    };
    /**
     * Manually save the database
     * By default you can't save the database if it's not loaded
     * @param force force the save of the database
     */
    JsonDB.prototype.save = function (force) {
        force = force || false;
        if (!force && !this.loaded) {
            throw new DatabaseError("DataBase not loaded. Can't write", 7);
        }
        var data = "";
        try {
            if (this.humanReadable) {
                data = JSON.stringify(this.data, null, 4);
            }
            else {
                data = JSON.stringify(this.data);
            }
            FS.writeFileSync(this.filename, data, 'utf8');
        } catch (err) {
            var error = new DatabaseError("Can't save the database", 2, err);
            error.inner = err;
            throw error;
        }

    };

    module.exports = JsonDB;
})();<|endoftext|>


<commit_before>class Player
{
    constructor( x, y, config)
    {
        this._x = x;
        this._y = y;
        this.config = config;

        this.color = color( 30, 144, 255);
        this._width = 1;
        this._height = 1;
        this.vision = 6;
    }

    get X()
    {
        return this._x;
    }
    set X( val )
    {
        this._x = val;
    }
    get Col()
    {
        return this._x;
    }

    get Y()
    {
        return this._y;
    }
    set Y( val )
    {
        this._y = val;
    }
    get Row()
    {
        return this._y;
    }

    get Width()
    {
        return this._width * this.config.grid.colWidth;
    }

    get Height()
    {
        return this._height * this.config.grid.rowHeight;
    }

    keyPressed( keyEvent )
    {
        if( keyEvent.keyCode === LEFT_ARROW )
        {
            if( this.X > 0)
                this.X--;
        }
        else if( keyEvent.keyCode === RIGHT_ARROW )
        {
            this.X++;
            if( this.X == this.config.grid.cols )
                this.X--;
        }
        else if( keyEvent.keyCode === DOWN_ARROW )
        {
            this.Y++;
            if( this.Y == this.config.grid.rows)
                this.Y--;
        }
        else if( keyEvent.keyCode === UP_ARROW )
        {
            if( this.Y > 0)
                this.Y--;
        }
    }

    update()
    {

    }

    draw()
    {
        //fill( 255, 255, 0, 50 )
        //ellipse( this.X + this.Width/2, this.Y + this.Height/2, this.gridSize * this.vision, this.gridSize * this.vision);

        fill( this.color );
        rect( this.X * this.config.grid.colWidth, this.Y * this.config.grid.rowHeight, this.Width, this.Height );

    }
}<commit_msg>simplified class for restarting purposes, basic movement keys<commit_after>class Player
{
    constructor( x, y, cell_size)
    {
        this.x = x;
        this.y = y;
        this.cell_size = cell_size;
        this.color = color( 30, 144, 255);
        this.item = new Item(0, 0, cell_size);
    }

    keyPressed( keyEvent )
    {
        if( keyEvent.keyCode === LEFT_ARROW )
        {
            this.x -= (this.x - this.cell_size < 0) ? 0 : this.cell_size;
        }
        else if( keyEvent.keyCode === RIGHT_ARROW )
        {
            this.x += (this.x = this.cell_size > width)? 0 : this.cell_size;
        }
        else if( keyEvent.keyCode === DOWN_ARROW )
        {
            this.y += (this.y + this.cell_size *2 > height ) ? 0 : this.cell_size
        }
        else if( keyEvent.keyCode === UP_ARROW )
        {
            this.y -= (this.y - this.cell_size < 0 ) ? 0 : this.cell_size;
        }
        if( this.item !== null )
        {
            this.item.updatePositionBasedOnPlayer( this.x, this.y );
        }
    }

    update()
    {
        if( this.item != null )
        {

        }
    }

    draw()
    {
        //fill( 255, 255, 0, 50 )
        //ellipse( this.X + this.Width/2, this.Y + this.Height/2, this.gridSize * this.vision, this.gridSize * this.vision);

        fill( this.color );
        rect( this.x, this.y , this.cell_size, this.cell_size );
        if( this.item !== null )
        {
            this.item.draw();
        }
    }
}<|endoftext|>


<commit_before>// Copyright (C) 2016 Goom Inc. All rights reserved.

import React, { PropTypes } from 'react';

import numberUtil from 'commons/utils/numberUtil';

export default React.createClass({
  propTypes: {
    order: PropTypes.object,
  },
  contextTypes: {
    activeCurrency: PropTypes.string,
    currencySign: PropTypes.object,
  },
  render() {
    const { order } = this.props;
    const { activeCurrency, currencySign } = this.context;
    const formatPrice = (type) => {
      return numberUtil.formatPrice(order[`${type}${activeCurrency}`], activeCurrency, currencySign);
    };
    const orderPrice = numberUtil.formatPrice(
      +(order[`tax${activeCurrency}`] || 0) + +(order[`handlingFee${activeCurrency}`] || 0) + +(order[`shippingCost${activeCurrency}`] || 0),
      activeCurrency,
      currencySign,
    );
    const subtotalPrice = formatPrice('subtotal');
    const taxPrice = formatPrice('tax');
    const handlingFeePrice = formatPrice('handlingFee');
    const shippingCostPrice = formatPrice('shippingCost');
    const totalPrice = formatPrice('total');
    return (

상품금액

{subtotalPrice}

주문비용

{orderPrice}

기타

0

결제금액

{totalPrice}

+

상품가격: {subtotalPrice}

+

사입비: {handlingFeePrice}

+

배송비: {shippingCostPrice}

+

부가세: {taxPrice}

=

{totalPrice}

    );
  },
});
<commit_msg>render adjustment info.<commit_after>// Copyright (C) 2016 Goom Inc. All rights reserved.

import React, { PropTypes } from 'react';
import _ from 'lodash';

import numberUtil from 'commons/utils/numberUtil';
import orderUtil from 'commons/utils/orderUtil';

export default React.createClass({
  propTypes: {
    order: PropTypes.object,
  },
  contextTypes: {
    activeCurrency: PropTypes.string,
    currencySign: PropTypes.object,
  },
  render() {
    const { order } = this.props;
    const { activeCurrency, currencySign } = this.context;
    const formatPrice = (type) => {
      return numberUtil.formatPrice(order[`${type}${activeCurrency}`], activeCurrency, currencySign);
    };
    const orderPrice = numberUtil.formatPrice(
      +(order[`tax${activeCurrency}`] || 0) + +(order[`handlingFee${activeCurrency}`] || 0) + +(order[`shippingCost${activeCurrency}`] || 0),
      activeCurrency,
      currencySign,
    );
    const brands = _.groupBy(order.orderProducts.map((o) => o.brand), 'id');
    const brandName = (brandId) => _.get(brands, [brandId, 0, 'name', 'ko'], brandId);
    const renderAdjustment = (adjustment) =>
    (

+

{brandName(adjustment.brandId)}: {numberUtil.formatPrice(adjustment[activeCurrency], activeCurrency, currencySign)}

    );
    const subtotalPrice = formatPrice('subtotal');
    const adjustmentPrice = formatPrice('adjustmentTotal');
    const taxPrice = formatPrice('tax');
    const handlingFeePrice = formatPrice('handlingFee');
    const shippingCostPrice = formatPrice('shippingCost');
    const totalPrice = formatPrice('total');
    return (

상품금액

{subtotalPrice}

주문비용

{orderPrice}

기타

{adjustmentPrice}

결제금액

{totalPrice}

+

상품가격: {subtotalPrice}

+

사입비: {handlingFeePrice}

+

배송비: {shippingCostPrice}

+

부가세: {taxPrice}

            {_.get(order, 'adjustments', []).map(renderAdjustment)}

=

{totalPrice}

    );
  },
});
<|endoftext|>