<commit_before>var _ = require('lodash');
var mongojs = require('mongojs');
var mongoose = require('mongoose')
var inflect = require('i')();
var tinySSE = require('tiny-sse');
var Promise = require('bluebird');
var hl = require('highland');
var JSONAPI_Error = require('./jsonapi-error');

/*
Usage:
======================================
When setting up Multi SSE (ie: SSE for multiple resources), you just need to pass in Harvester context as such:

this.multiSSE = new SSE();
this.multiSSE.init({
    context: harvesterApp
});

You can then point an EventReader to listen from "{base_url}/changes/stream?resouces=foo,bar,baz".

When setting up SSE for a single route, you will need to pass the resource name:

this.singleSSE = new SSE();
this.singleSSE.init({
    context: harvesterApp,
    singleResourceName: 'foo'
});

You can then point an EventReader to listen from "{base_url}/foo/changes/stream".

Verbs:
======================================
You can also pass a "verbs" option to this module. If none is passed, SSE will only listen to "insert" events from uplog.
Values you can pass are "post", "put" and "delete" which in turn currespond to oplog ops "i", "u" and "d".

this.singleSSE = new SSE();
this.singleSSE.init({
    context: harvesterApp,
    singleResourceName: 'foo',
    verbs: ['post', 'put', 'delete']
});
*/

var SSE = function() {
};

SSE.prototype.init = function(config) {
    this.config = config;
    this.options = config.context.options;
    this.harvesterApp = config.context;

    //only listen to post events if the verb is not specified
    this.verbs = config.verbs || ['post'];

    //wraps it up in an array of single item, so that it fits the current logic without too many conditions
    this.singleResourceName = config.singleResourceName && [config.singleResourceName];
    var that = this;
    this.db =  mongoose.createConnection(this.options.oplogConnectionString);
    var routePrefix =  '';

    if (config.singleResourceName) {
        var pluralName = (this.options.inflect) ? inflect.pluralize(config.singleResourceName) : config.singleResourceName;

        routePrefix = '/' + pluralName;
    }

    this.harvesterApp.router.get(this.options.baseUrl + routePrefix + '/changes/stream', this.requestValidationMiddleware.bind(this), tinySSE.head(), tinySSE.ticker({seconds: 3}), this.handler.bind(this));
};

SSE.prototype.requestValidationMiddleware = function (req, res, next) {
    routeNames = req.query.resources ? req.query.resources.split(',') : [];

    if (this.singleResourceName) {
        routeNames = this.singleResourceName;
    }

    if (routeNames.length === 0) {
        throw new JSONAPI_Error({
            status: 400,
            title: 'Requested changes on missing resource',
            detail: 'You have not specified any resources, please do so by providing "resource?foo,bar" as query'
        });
    }

    if (!this.allResourcesExist(routeNames)) {
        throw new JSONAPI_Error({
            status: 400,
            title: 'Requested changes on missing resource',
            detail: 'The follow resources don\'t exist ' +  this.getMissingResources(routeNames).join(',')
        });
    }

    if (req.headers['last-event-id']) {
        var tsSplit = _.map(req.headers['last-event-id'].split('_'), function (item) {
            return parseInt(item, 10);
        });

        isValidTS = _.all(tsSplit, function(ts) {
            return !isNaN(ts);
        });

        if(!isValidTS) {
            throw new JSONAPI_Error({
                status: 400,
                title: 'Invalid Timestamp',
                detail: 'Could not parse the time stamp provided'
            });
        }
    }

    next();
};

SSE.prototype.handler = function (req, res, next) {

    var that = this;
    var options = {
        tailable: true,
        awaitdata: true,
        oplogReplay: true,
        numberOfRetries: Number.MAX_VALUE
    };

    routeNames = req.query.resources ? req.query.resources.split(',') : [];

    if (this.singleResourceName) {
        routeNames = this.singleResourceName;
    }

    var regex = new RegExp('.*\\.(' + routeNames.join('|') + ')', 'i');
    var docStream = hl();

    this.getQuery(req, regex)
    .then(function(query) {
            var collection = that.db.collection('oplog.rs')
            var stream = collection.find(query, options).stream();
            var docStream = hl(stream);

            docStream.resume();

            var consume = hl().consume(function(err, chunk, push, next) {
                var resourceNames = _.map(routeNames, function(routeName) {
                    var pluralName = (that.options.inflect) ? inflect.pluralize(routeName) : routeName;
                    return new RegExp(pluralName, 'i');
                });

                var matchesEitherResource = _.some(resourceNames, function(resourceName) {
                    return resourceName.test(chunk.ns);
                });

                if (matchesEitherResource) {
                    var id = chunk.ts.getHighBits() + '_' + chunk.ts.getLowBits();
                    var eventName = that.getEventName(routeNames, chunk);
                    var data = that.getData(routeNames[0], chunk);

                    var filters = that.getFilters(req);

                    var passedFilter = _.reduce(filters, function(obj, filter) {
                        return _.filter([data], _.matches(filter));
                    }, true);

                    //if we have filters, make sure they are passed
                    if (passedFilter.length > 0 || filters.length === 0) {
                        tinySSE.send({id: id, event: eventName, data: data})(req, res);
                    }

                    next();
                }
            });

            docStream.through(consume).errors(function(err) {
                console.log('HARVESTER SSE ERROR>>> ' + err.stack)
                that.handleError(err, res, docStream);
            });
    })
    .catch(function(err) {
        console.log('HARVESTER SSE ERROR>>> ' + err.stack)
        that.handleError(err, res, docStream);
    });
};

SSE.prototype.handleError = function(err, res, docStream) {
    res.end();
    if(docStream) {
        docStream.destroy();
    }
};

SSE.prototype.allResourcesExist = function(resourceNames) {
    return this.getMissingResources(resourceNames).length === 0;
};

SSE.prototype.getMissingResources = function(resourceNames) {

    var harvesterResourceNames = this.resourceName || _.keys(this.harvesterApp.createdResources);

    return _.difference(resourceNames, harvesterResourceNames);
}

SSE.prototype.getQuery = function(req, ns) {
    var lastEventId = req.headers['last-event-id'];
    var coll = this.db.collection('oplog.rs');

    var verbs = this.verbs.map(function(verb) {
        return {
            'post' : 'i',
            'put' : 'u',
            'delete' : 'd'
        }[verb]
    });

    var query = {
        ns : ns,
        op : new RegExp('(' + verbs.join('|') + ')', 'i')
    };
    return new Promise(function(resolve, reject) {
        if (req.headers['last-event-id']) {
            var tsSplit = _.map(lastEventId.split('_'), function (item) {
                return parseInt(item, 10);
            });

            query.ts = {
                $gt: new mongojs.Timestamp(tsSplit[1], tsSplit[0])
            };

            return resolve(query);
        }

        var foundResults = false;

        coll.find(query, {sort : {$natural : -1}, limit : 1}).each(function(err, item) {
            if (err) return reject(err);
            if (item) foundResults = true;
            if(!foundResults) {
                return coll.find({}, {sort : {$natural : -1}, limit : 1}).each(function(err, item) {
                    if (err) return reject(err);

                    if(!item) return;

                    query.ts = {
                        $gt: item.ts
                    };

                    return resolve(query);
                });
            }
            if(!item) return;
            query.ts = {
                $gt: item.ts
            };

            return resolve(query);
        });
    });
};

SSE.prototype.getFilters = function(req) {

    var filters = _.chain(req.query)
    .map(function(item, key) {
        if (!_.contains(['limit', 'sort', 'offset', 'resources'], key)) {
            var filter = {};
            filter[key] = item;
            return filter;
        }
    })
    .filter(function(item) {
        return !!item;
    })
    //converts {'foo.bar' : 'foobar'} to {foo : { bar : 'foobar' }}
    .map(function(item) {
        var keys = _.keys(item)[0].split('.');
        return _.reduce(keys, function(obj, key, index) {

            var value = (index === keys.length - 1 || keys.length === 1) ? _.values(item)[0] : {};

            if (index === 0) {
                obj[key] = (keys.length > 1) ? {} : value;
            } else {
                obj[keys[index - 1]][key] = value;
            }
            return obj;
        }, {});
    })
    .value();

    return filters;

};

SSE.prototype.getData = function(routeName, chunk) {
    var data;
    var model = this.harvesterApp.adapter.model(routeName);

    switch (chunk.op) {
        case 'i' : data = this.harvesterApp.adapter._deserialize(model, chunk.o); break;
        case 'u' : data = chunk.o.$set; break;
        default : data = {}
    }

    return data;
};

SSE.prototype.getEventName = function(routeNames, chunk) {
    return inflect.pluralize(routeNames[0]) + '_' + chunk.op;
};

module.exports = SSE;
<commit_msg>removed ns from initial query<commit_after>var _ = require('lodash');
var mongojs = require('mongojs');
var mongoose = require('mongoose')
var inflect = require('i')();
var tinySSE = require('tiny-sse');
var Promise = require('bluebird');
var hl = require('highland');
var JSONAPI_Error = require('./jsonapi-error');

/*
Usage:
======================================
When setting up Multi SSE (ie: SSE for multiple resources), you just need to pass in Harvester context as such:

this.multiSSE = new SSE();
this.multiSSE.init({
    context: harvesterApp
});

You can then point an EventReader to listen from "{base_url}/changes/stream?resouces=foo,bar,baz".

When setting up SSE for a single route, you will need to pass the resource name:

this.singleSSE = new SSE();
this.singleSSE.init({
    context: harvesterApp,
    singleResourceName: 'foo'
});

You can then point an EventReader to listen from "{base_url}/foo/changes/stream".

Verbs:
======================================
You can also pass a "verbs" option to this module. If none is passed, SSE will only listen to "insert" events from uplog.
Values you can pass are "post", "put" and "delete" which in turn currespond to oplog ops "i", "u" and "d".

this.singleSSE = new SSE();
this.singleSSE.init({
    context: harvesterApp,
    singleResourceName: 'foo',
    verbs: ['post', 'put', 'delete']
});
*/

var SSE = function() {
};

SSE.prototype.init = function(config) {
    this.config = config;
    this.options = config.context.options;
    this.harvesterApp = config.context;

    //only listen to post events if the verb is not specified
    this.verbs = config.verbs || ['post'];

    //wraps it up in an array of single item, so that it fits the current logic without too many conditions
    this.singleResourceName = config.singleResourceName && [config.singleResourceName];
    var that = this;
    this.db =  mongoose.createConnection(this.options.oplogConnectionString);
    var routePrefix =  '';

    if (config.singleResourceName) {
        var pluralName = (this.options.inflect) ? inflect.pluralize(config.singleResourceName) : config.singleResourceName;

        routePrefix = '/' + pluralName;
    }

    this.harvesterApp.router.get(this.options.baseUrl + routePrefix + '/changes/stream', this.requestValidationMiddleware.bind(this), tinySSE.head(), tinySSE.ticker({seconds: 3}), this.handler.bind(this));
};

SSE.prototype.requestValidationMiddleware = function (req, res, next) {
    routeNames = req.query.resources ? req.query.resources.split(',') : [];

    if (this.singleResourceName) {
        routeNames = this.singleResourceName;
    }

    if (routeNames.length === 0) {
        throw new JSONAPI_Error({
            status: 400,
            title: 'Requested changes on missing resource',
            detail: 'You have not specified any resources, please do so by providing "resource?foo,bar" as query'
        });
    }

    if (!this.allResourcesExist(routeNames)) {
        throw new JSONAPI_Error({
            status: 400,
            title: 'Requested changes on missing resource',
            detail: 'The follow resources don\'t exist ' +  this.getMissingResources(routeNames).join(',')
        });
    }

    if (req.headers['last-event-id']) {
        var tsSplit = _.map(req.headers['last-event-id'].split('_'), function (item) {
            return parseInt(item, 10);
        });

        isValidTS = _.all(tsSplit, function(ts) {
            return !isNaN(ts);
        });

        if(!isValidTS) {
            throw new JSONAPI_Error({
                status: 400,
                title: 'Invalid Timestamp',
                detail: 'Could not parse the time stamp provided'
            });
        }
    }

    next();
};

SSE.prototype.handler = function (req, res, next) {

    var that = this;
    var options = {
        tailable: true,
        awaitdata: true,
        oplogReplay: true,
        numberOfRetries: Number.MAX_VALUE
    };

    routeNames = req.query.resources ? req.query.resources.split(',') : [];

    if (this.singleResourceName) {
        routeNames = this.singleResourceName;
    }

    var regex = new RegExp('.*\\.(' + routeNames.join('|') + ')', 'i');
    var docStream = hl();

    this.getQuery(req, regex)
    .then(function(query) {
            var collection = that.db.collection('oplog.rs')
            var stream = collection.find(query, options).stream();
            var docStream = hl(stream);

            docStream.resume();

            var consume = hl().consume(function(err, chunk, push, next) {
                var resourceNames = _.map(routeNames, function(routeName) {
                    var pluralName = (that.options.inflect) ? inflect.pluralize(routeName) : routeName;
                    return new RegExp(pluralName, 'i');
                });

                var matchesEitherResource = _.some(resourceNames, function(resourceName) {
                    return resourceName.test(chunk.ns);
                });

                if (matchesEitherResource) {
                    var id = chunk.ts.getHighBits() + '_' + chunk.ts.getLowBits();
                    var eventName = that.getEventName(routeNames, chunk);
                    var data = that.getData(routeNames[0], chunk);

                    var filters = that.getFilters(req);

                    var passedFilter = _.reduce(filters, function(obj, filter) {
                        return _.filter([data], _.matches(filter));
                    }, true);

                    //if we have filters, make sure they are passed
                    if (passedFilter.length > 0 || filters.length === 0) {
                        tinySSE.send({id: id, event: eventName, data: data})(req, res);
                    }

                    next();
                }
            });

            docStream.through(consume).errors(function(err) {
                console.log('HARVESTER SSE ERROR>>> ' + err.stack)
                that.handleError(err, res, docStream);
            });
    })
    .catch(function(err) {
        console.log('HARVESTER SSE ERROR>>> ' + err.stack)
        that.handleError(err, res, docStream);
    });
};

SSE.prototype.handleError = function(err, res, docStream) {
    res.end();
    if(docStream) {
        docStream.destroy();
    }
};

SSE.prototype.allResourcesExist = function(resourceNames) {
    return this.getMissingResources(resourceNames).length === 0;
};

SSE.prototype.getMissingResources = function(resourceNames) {

    var harvesterResourceNames = this.resourceName || _.keys(this.harvesterApp.createdResources);

    return _.difference(resourceNames, harvesterResourceNames);
}

SSE.prototype.getQuery = function(req, ns) {
    var lastEventId = req.headers['last-event-id'];
    var coll = this.db.collection('oplog.rs');

    var verbs = this.verbs.map(function(verb) {
        return {
            'post' : 'i',
            'put' : 'u',
            'delete' : 'd'
        }[verb]
    });

    var query = {
        ns : ns,
        op : new RegExp('(' + verbs.join('|') + ')', 'i')
    };
    return new Promise(function(resolve, reject) {
        if (req.headers['last-event-id']) {
            var tsSplit = _.map(lastEventId.split('_'), function (item) {
                return parseInt(item, 10);
            });

            query.ts = {
                $gt: new mongojs.Timestamp(tsSplit[1], tsSplit[0])
            };

            return resolve(query);
        }

        coll.find({op : query.op}, {sort : {$natural : -1}, limit : 1}).toArray(function(err, items) {
            if (err || !items) return reject(err);
            query.ts = {
                $gt: items[0].ts
            };

            return resolve(query);
        });
    });
};

SSE.prototype.getFilters = function(req) {

    var filters = _.chain(req.query)
    .map(function(item, key) {
        if (!_.contains(['limit', 'sort', 'offset', 'resources'], key)) {
            var filter = {};
            filter[key] = item;
            return filter;
        }
    })
    .filter(function(item) {
        return !!item;
    })
    //converts {'foo.bar' : 'foobar'} to {foo : { bar : 'foobar' }}
    .map(function(item) {
        var keys = _.keys(item)[0].split('.');
        return _.reduce(keys, function(obj, key, index) {

            var value = (index === keys.length - 1 || keys.length === 1) ? _.values(item)[0] : {};

            if (index === 0) {
                obj[key] = (keys.length > 1) ? {} : value;
            } else {
                obj[keys[index - 1]][key] = value;
            }
            return obj;
        }, {});
    })
    .value();

    return filters;

};

SSE.prototype.getData = function(routeName, chunk) {
    var data;
    var model = this.harvesterApp.adapter.model(routeName);

    switch (chunk.op) {
        case 'i' : data = this.harvesterApp.adapter._deserialize(model, chunk.o); break;
        case 'u' : data = chunk.o.$set; break;
        default : data = {}
    }

    return data;
};

SSE.prototype.getEventName = function(routeNames, chunk) {
    return inflect.pluralize(routeNames[0]) + '_' + chunk.op;
};

module.exports = SSE;
<|endoftext|>


<commit_before>function dockmanShow(name) {
    return document.getElementById(name).style.display='block'
}

function dockmanHide(name) {
    return document.getElementById(name).style.display='none'
}
<commit_msg>close modal if clicked outside of it<commit_after>function dockmanShow(name) {
    var modal = document.getElementById(name)
    window.onclick = function(event) {
        if (event.target == modal) {
            modal.style.display = 'none';
        }
    }
    modal.style.display = 'block'
}

function dockmanHide(name) {
    document.getElementById(name).style.display = 'none'
}
<|endoftext|>


<commit_before>'use strict';

var Connection = require('ssh2'),
    extend = require('xtend');

/**
 * Creates a new SSH connection
 * @param {Object}  config
 * @param {String}  config.host        - Host to connect to
 * @param {Number} [config.port=22]    - Port number
 * @param {String}  config.user        - Username
 * @param {String}  config.pass        - Password
 * @param {String} [config.baseDir=''] - Base directory to perform commands from
 * @constructor
 */
var SSH = function(config) {
    config = extend({
        host: '',
        port: 22,
        user: '',
        pass: '',
        baseDir: ''
    }, config);

    this._c = new Connection();
    this._commands = [];

    this.host = config.host;
    this.port = config.port;
    this.user = config.user;
    this.pass = config.pass;
    this.baseDir = config.baseDir;
};

/**
 * Helper function for queueing up the next command in the chain
 * @param {Number} index - Array index of the command to perform next
 * @private
 */
SSH.prototype._queueCommand = function(index) {
    if (this._commands.length <= index) {
        return;
    }

    var self = this,
        command = this._commands[index],
        cmd = command.cmd;

    // Start by going to the base directory, if set
    if (this.baseDir.length > 0) {
        cmd = 'cd ' + this.baseDir + ' && ' + cmd;
    }

    // Execute the command
    this._c.exec(cmd, { pty: command.pty }, function(err, stream) {
        if (err) {
            command.handlers.err(err);
            return;
        }

        var passwordSent = false,
            buffer = '',
            stdout = '',
            stderr = '';

        stream.on('data', function(data, extended) {
            // This will allow us to send in the password if we're doing something like `sudo`
            buffer += (!passwordSent) ? data.toString() : '';

            if (!passwordSent && buffer.substr(-2) === ': ') {
                stream.write(self.pass + '\n');
                buffer = '';
                passwordSent = true;
            } else {
                data = data.toString();
                if (extended === 'stderr') {
                    stderr += data;
                    command.handlers.err(data);
                } else {
                    stdout += data;
                    command.handlers.out(data);
                }
            }
        });

        stream.on('exit', function(code) {
            var numOfCommands = self._commands.length,
                executeNext = (command.handlers.exit(+code, stdout, stderr) !== false);

            // Queue up the next command
            if (executeNext && index < self._commands.length - 1) {
                self._queueCommand(index + 1);
            } else if (!executeNext) {
                // If the commands have been modified during the last exit, use those
                if (numOfCommands !== self._commands.length) {
                    self._commands.splice(0, numOfCommands);
                    self._queueCommand(0);
                } else {
                    self._c.end();
                }
            } else if (index === self._commands.length - 1) {
                self._c.end();
            }
        });
    });
};

<commit_msg>Added the main exec function for generating commands.<commit_after>'use strict';

var Connection = require('ssh2'),
    extend = require('xtend');

/**
 * Creates a new SSH connection
 * @param {Object}  config
 * @param {String}  config.host        - Host to connect to
 * @param {Number} [config.port=22]    - Port number
 * @param {String}  config.user        - Username
 * @param {String}  config.pass        - Password
 * @param {String} [config.baseDir=''] - Base directory to perform commands from
 * @constructor
 */
var SSH = function(config) {
    config = extend({
        host: '',
        port: 22,
        user: '',
        pass: '',
        baseDir: ''
    }, config);

    this._c = new Connection();
    this._commands = [];

    this.host = config.host;
    this.port = config.port;
    this.user = config.user;
    this.pass = config.pass;
    this.baseDir = config.baseDir;
};

/**
 * Helper function for queueing up the next command in the chain
 * @param {Number} index - Array index of the command to perform next
 * @private
 */
SSH.prototype._queueCommand = function(index) {
    if (this._commands.length <= index) {
        return;
    }

    var self = this,
        command = this._commands[index],
        cmd = command.cmd;

    // Start by going to the base directory, if set
    if (this.baseDir.length > 0) {
        cmd = 'cd ' + this.baseDir + ' && ' + cmd;
    }

    // Execute the command
    this._c.exec(cmd, { pty: command.pty }, function(err, stream) {
        if (err) {
            command.handlers.err(err);
            return;
        }

        var passwordSent = false,
            buffer = '',
            stdout = '',
            stderr = '';

        stream.on('data', function(data, extended) {
            // This will allow us to send in the password if we're doing something like `sudo`
            buffer += (!passwordSent) ? data.toString() : '';

            if (!passwordSent && buffer.substr(-2) === ': ') {
                stream.write(self.pass + '\n');
                buffer = '';
                passwordSent = true;
            } else {
                data = data.toString();
                if (extended === 'stderr') {
                    stderr += data;
                    command.handlers.err(data);
                } else {
                    stdout += data;
                    command.handlers.out(data);
                }
            }
        });

        stream.on('exit', function(code) {
            var numOfCommands = self._commands.length,
                executeNext = (command.handlers.exit(+code, stdout, stderr) !== false);

            // Queue up the next command
            if (executeNext && index < self._commands.length - 1) {
                self._queueCommand(index + 1);
            } else if (!executeNext) {
                // If the commands have been modified during the last exit, use those
                if (numOfCommands !== self._commands.length) {
                    self._commands.splice(0, numOfCommands);
                    self._queueCommand(0);
                } else {
                    self._c.end();
                }
            } else if (index === self._commands.length - 1) {
                self._c.end();
            }
        });
    });
};

/**
 * Queues up a command for execution
 * @param {String}            command            - Command to execute
 * @param {Object}            options
 * @param {String[]}         [options.args]      - Additional command line arguments
 * @param {Function(String)} [options.out]       - Called to capture stdout
 * @param {Function(String)} [options.err]       - Called to capture stderr
 * @param {Function(Number)} [options.exit]      - Called when the command exits
 * @param {Boolean}          [options.pty=false] - If true, allocates a pseudo-tty (useful for sudo)
 * @return {SSH}
 */
SSH.prototype.exec = function(command, options) {
    options = extend({
        args: null,
        out:  function(){},
        err:  function(){},
        exit: function(){},
        pty:  false
    }, options);

    // Apply additional command line arguments
    if (options.args !== null) {
        for (var i = 0, len = options.args.length; i < len; i++) {
            command += ' ' + options.args[i];
        }
    }

    this._commands.push({
        cmd: command,
        handlers: {
            out:  options.out,
            err:  options.out,
            exit: options.exit
        },
        pty: options.pty
    });

    return this;
};

module.exports = SSH;<|endoftext|>


<commit_before>const fs = require('fs-extra')
const path = require('path')
const appRoot = require('app-root-path').path
const _infoForPkg = require('../core/_info-for-pkg')
const writePackageInfoToUnpmPackages = require('./write-package-info-to-unpm-packages')
const {syncMissingMetaFiles} = require('node-unity-guid-utils')

 /**
  * For a package that's already (npm) installed to node_modules,
  * unpm.installPackageToUnity installs that package to unity
  * (typically just the source) at
  * Assets/Plugins/[packages/][${scope}/]${package_name}
  *
  * Typically, this is called from a postinstall script specified in the package
  *
  * @param {string} pkgName package we will install to unity Assets
  *     (note this must already be installed to node_modules)
  *
  * @param {object} opts.project_root
  *   optional unity project root (directy that contains unity's 'Assets' folder)
  *   When not passed, this defaults to the app-root-path
  *   as determined by eponymous npm package:
  *   https://www.npmjs.com/package/app-root-path
  *
  * @param {string} options.package_scope - the scope the package
  *      that will be copied back from under Assets/Plugins,
  *      so the actual package path is (unity)/Assets/Plugins/$scope/$package_name.
  *      If nothing passed, takes the value from package.json 'scope' property.
  *
  * @param {string} options.src_path - name (or rel path)
  *      of the folder within the package that contains the pkg source for unity.
  *      If nothing passed, defaults to 'Runtime'
  *
  * @returns {object} @see _infoForPkg
  */
const installPackageToUnity = async (pkgName, opts) => {

  opts = opts || {}

  pkgName = path.basename(pkgName)

  const projRoot = opts.project_root || appRoot

  const pkgRoot = path.join(projRoot, 'node_modules', pkgName)

  if(await fs.exists(path.join(pkgRoot, 'package.json')) === false) {
    if(opts.verbose) {
      console.log(`installPackageToUnity skipping because package not installed in node_modules
        pkg=${pkgName} proj=${projRoot}`)
    }

    return {
      skippedInstallReason: 'package not installed in root of unity project'
    }
  }

  info = await _infoForPkg(pkgRoot, opts)

  if(opts.verbose) {
      console.log(`info for package at root ${pkgRoot}=\n${JSON.stringify(info, null, 2)}`)
  }

  if (!info.is_module) {
      return {
          skipped_install_reason: 'package not installed in root of unity project',
          package_info: info
      }
  }

  const result = await writePackageInfoToUnpmPackages(pkgName, {
    ...opts,
    project_root: projRoot,
    transform_package: async (p) => {
        return {
            ...p,
            package_unity_src: info.package_unity_src,
            package_unity_samples: info.package_unity_samples,
            unity_install_path: info.unity_install_path,
            unity_install_samples_path: info.unity_install_samples_path,
            unity_install: {
                path: info.unity_install_path,
                src: info.package_unity_src
            }
        }
    }
  })

  if(opts.verbose) {
      console.log(`installPackageToUnity(${pkgName}) result:\n ${JSON.stringify(result, null, 2)}`)
  }

  const pkgEntry = result.unpmPackages.packages[pkgName]

  await fs.ensureDir(pkgEntry.unity_install.path)
  await fs.copy(pkgEntry.unity_install.src, pkgEntry.unity_install.path, {
    filter: f => f.match(/.*\.meta$/) === false
  })
  await syncMissingMetaFiles({
    path_source: pkgEntry.unity_install.src,
    path_target: pkgEntry.unity_install.path
  })

  if(await fs.exists(pkgEntry.package_unity_samples)) {
      await fs.ensureDir(pkgEntry.unity_install_samples_path)
      await fs.copy(pkgEntry.package_unity_samples, pkgEntry.unity_install_samples_path, {
        filter: f => f.match(/.*\.meta$/) === false
      })
      await syncMissingMetaFiles({
        path_source: pkgEntry.package_unity_samples,
        path_target: pkgEntry.unity_install_samples_path
      })
  }

  return result
}

module.exports = installPackageToUnity
<commit_msg>fixed regex<commit_after>const fs = require('fs-extra')
const path = require('path')
const appRoot = require('app-root-path').path
const _infoForPkg = require('../core/_info-for-pkg')
const writePackageInfoToUnpmPackages = require('./write-package-info-to-unpm-packages')
const {syncMissingMetaFiles} = require('node-unity-guid-utils')

 /**
  * For a package that's already (npm) installed to node_modules,
  * unpm.installPackageToUnity installs that package to unity
  * (typically just the source) at
  * Assets/Plugins/[packages/][${scope}/]${package_name}
  *
  * Typically, this is called from a postinstall script specified in the package
  *
  * @param {string} pkgName package we will install to unity Assets
  *     (note this must already be installed to node_modules)
  *
  * @param {object} opts.project_root
  *   optional unity project root (directy that contains unity's 'Assets' folder)
  *   When not passed, this defaults to the app-root-path
  *   as determined by eponymous npm package:
  *   https://www.npmjs.com/package/app-root-path
  *
  * @param {string} options.package_scope - the scope the package
  *      that will be copied back from under Assets/Plugins,
  *      so the actual package path is (unity)/Assets/Plugins/$scope/$package_name.
  *      If nothing passed, takes the value from package.json 'scope' property.
  *
  * @param {string} options.src_path - name (or rel path)
  *      of the folder within the package that contains the pkg source for unity.
  *      If nothing passed, defaults to 'Runtime'
  *
  * @returns {object} @see _infoForPkg
  */
const installPackageToUnity = async (pkgName, opts) => {

  opts = opts || {}

  pkgName = path.basename(pkgName)

  const projRoot = opts.project_root || appRoot

  const pkgRoot = path.join(projRoot, 'node_modules', pkgName)

  if(await fs.exists(path.join(pkgRoot, 'package.json')) === false) {
    if(opts.verbose) {
      console.log(`installPackageToUnity skipping because package not installed in node_modules
        pkg=${pkgName} proj=${projRoot}`)
    }

    return {
      skippedInstallReason: 'package not installed in root of unity project'
    }
  }

  info = await _infoForPkg(pkgRoot, opts)

  if(opts.verbose) {
      console.log(`info for package at root ${pkgRoot}=\n${JSON.stringify(info, null, 2)}`)
  }

  if (!info.is_module) {
      return {
          skipped_install_reason: 'package not installed in root of unity project',
          package_info: info
      }
  }

  const result = await writePackageInfoToUnpmPackages(pkgName, {
    ...opts,
    project_root: projRoot,
    transform_package: async (p) => {
        return {
            ...p,
            package_unity_src: info.package_unity_src,
            package_unity_samples: info.package_unity_samples,
            unity_install_path: info.unity_install_path,
            unity_install_samples_path: info.unity_install_samples_path,
            unity_install: {
                path: info.unity_install_path,
                src: info.package_unity_src
            }
        }
    }
  })

  if(opts.verbose) {
      console.log(`installPackageToUnity(${pkgName}) result:\n ${JSON.stringify(result, null, 2)}`)
  }

  const pkgEntry = result.unpmPackages.packages[pkgName]

  await fs.ensureDir(pkgEntry.unity_install.path)
  await fs.copy(pkgEntry.unity_install.src, pkgEntry.unity_install.path, {
    filter: f => f.match(/.*\.meta$/) === null
  })
  await syncMissingMetaFiles({
    path_source: pkgEntry.unity_install.src,
    path_target: pkgEntry.unity_install.path
  })

  if(await fs.exists(pkgEntry.package_unity_samples)) {
      await fs.ensureDir(pkgEntry.unity_install_samples_path)
      await fs.copy(pkgEntry.package_unity_samples, pkgEntry.unity_install_samples_path, {
        filter: f => f.match(/.*\.meta$/) === null
      })
      await syncMissingMetaFiles({
        path_source: pkgEntry.package_unity_samples,
        path_target: pkgEntry.unity_install_samples_path
      })
  }

  return result
}

module.exports = installPackageToUnity
<|endoftext|>


<commit_before>
/**
 * Module dependencies
 */

var config    = require('./config.js');
var emitter   = require('events').EventEmitter;
var log       = require('logule').init(module, "usb");
var udev      = require('udev');
var util      = require('util');

/**
 * udev monitor
 */

var monitor = udev.monitor();

/**
 * ldap
 *
 * @constructor
 */

function usb() {

  var self = this;

  /**
   * usb detection handlers
   * @see https://github.com/KABA-CCEAC/node-usb-detection
   */

  monitor.on('add', function (device) {
    if(device.DEVTYPE == 'usb_device') {
      var serial = device.ID_SERIAL_SHORT;
      self.emit(serial);
    }
  });

  // ..

  log.info('init');
}

/**
 * ..
 */

util.inherits(usb, emitter);

/**
 * Expose `usb`
 * @type {usb}
 */

module.exports = new usb();
<commit_msg>whoops<commit_after>
/**
 * Module dependencies
 */

var emitter   = require('events').EventEmitter;
var log       = require('logule').init(module, "usb");
var udev      = require('udev');
var util      = require('util');

/**
 * udev monitor
 */

var monitor = udev.monitor();

/**
 * ldap
 *
 * @constructor
 */

function usb() {

  var self = this;

  /**
   * usb detection handlers
   * @see https://github.com/KABA-CCEAC/node-usb-detection
   */

  monitor.on('add', function (device) {
    if(device.DEVTYPE == 'usb_device') {
      var serial = device.ID_SERIAL_SHORT;
      self.emit('add', serial);
    }
  });

  // ..

  log.info('init');
}

/**
 * ..
 */

util.inherits(usb, emitter);

/**
 * Expose `usb`
 * @type {usb}
 */

module.exports = new usb();
<|endoftext|>


<commit_before>var cp = require('child_process');
var fs = require('fs');
var path = require('path');
var spawn = require('child_process').spawn;
var util = require('util');
if (!fs.existsSync) {
    fs.existsSync = path.existsSync;
}

function compact(array) {
    return array.filter(function (i) {
        if (typeof i.length !== 'undefined')
            return i.length > 0;
        return !!i;
    });
}

function findCmd(name) {
    "use strict";

    var paths = process.env['PATH'].split(':');
    var pathLen = paths.length;
    for (var i = 0; i < pathLen; i++) {
        var sp = path.resolve(paths[i]);
        var fname = path.normalize(path.join(sp, name));
        if (fs.existsSync(fname)) {
            return fname;
        }
    }

    return null;
}

var zfsBin = findCmd('zfs');
function zfs(args, callback) {
    "use strict";

    cp.execFile(zfsBin, args, {maxBuffer: 8000000}, function (err, stdout, stderr) {
        if (callback && typeof callback === 'function') {
            if (err) {
                err.message = compact(err.message.split('\n')).join('; ').trim();
                callback(err);
            } else {
                callback(null, stdout);
            }
        }
    });
}

function parseNumber(str) {
    var m = str.match(/^([0-9.]+)([KMGT]?)$/);
    if (!m) {
        return -1;
    }

    var n = parseFloat(m[1]);
    if (m[2] === 'K') {
        n *= 1024;
    } else if (m[2] === 'M') {
        n *= 1024 * 1024;
    } else if (m[2] === 'G') {
        n *= 1024 * 1024 * 1024;
    } else if (m[2] === 'T') {
        n *= 1024 * 1024 * 1024 * 1024;
    } else if (m[2] === 'P') {
        n *= 1024 * 1024 * 1024 * 1024 * 1024;
    }

    return Math.floor(n);
}

function ZFS(info) {
    "use strict";

    var obj = Object.create({});
    if (typeof info === 'string') {
        info = info.split(/\s+/);
    }

    if (info.length !== 5) {
        return null;
    }

    obj.name = info[0];
    obj.used = parseNumber(info[1]);
    if (info[2] !== '-') { // Snapshots don't have 'avail' field.
        obj.avail = parseNumber(info[2]);
    }
    obj.refer = parseNumber(info[3]);
    obj.mountpoint = info[4];

    Object.freeze(obj);
    return obj;
}

function Property(info) {
    "use strict";

    var obj = Object.create({});
    if (typeof info === 'string') {
        info = info.split(/\s+/, 4);
    }

    if (info.length !== 4) {
        return null;
    }

    obj.name = info[0];
    obj.property = info[1];
    obj.value = info[2];
    obj.source = info[3];

    Object.freeze(obj);
    return obj;
}

function list(opts, cb) {
    "use strict";

    if (typeof opts === 'function') {
        cb = opts;
        opts = undefined;
    }

    var params = ['list', '-H'];

    if (opts && opts.type) {
        params.push('-t');
        params.push(opts.type);
    }

    zfs(params, function (err, stdout) {
        if (cb && typeof cb === 'function') {
            var lines = compact(stdout.split('\n'));
            var list = lines.map(function (x) { return new ZFS(x); });
            cb(err, list);
        }
    });
}

function get(opts, cb) {
    "use strict";

    var params = [ 'get', '-pH' ];
    if (opts.source) {
        params.push('-s');
        params.push(opts.source);
    }
    params.push(opts.property);

    zfs(params, function (err, stdout) {
        if (cb && typeof cb === 'function') {
            var lines = compact(stdout.split('\n'));
            var list = lines.map(function (x) { return new Property(x); });
            cb(err, list);
        }
    });
}

function destroy(opts, cb) {
    "use strict";

    var params = [ 'destroy' ];
    if (opts.recursive) {
        params.push('-r');
    }
    params.push(opts.name);

    zfs(params, cb);
}

function create(opts, cb) {
    "use strict";

    var params = [ 'create' ];

    params.push(opts.name);

    zfs(params, cb);
}

function set(opts, cb) {
    "use strict";

    var params = [ 'set' ];

    params.push(opts.property + "=" + opts.value)

    params.push(opts.name);

    zfs(params, cb);
}

function snapshot(opts, cb) {
    "use strict";

    var params = [ 'snapshot' ];
    if (opts.recursive) {
        params.push('-r');
    }
    params.push(opts.dataset + '@' + opts.name);

    zfs(params, cb);
}

exports.get = get;
exports.set = set;
exports.list = list;
exports.destroy = destroy;
exports.create = create;
exports.snapshot = snapshot;
<commit_msg>handle failure of zfs list by handing it over to the callback<commit_after>var cp = require('child_process');
var fs = require('fs');
var path = require('path');
var spawn = require('child_process').spawn;
var util = require('util');
if (!fs.existsSync) {
    fs.existsSync = path.existsSync;
}

function compact(array) {
    return array.filter(function (i) {
        if (typeof i.length !== 'undefined')
            return i.length > 0;
        return !!i;
    });
}

function findCmd(name) {
    "use strict";

    var paths = process.env['PATH'].split(':');
    var pathLen = paths.length;
    for (var i = 0; i < pathLen; i++) {
        var sp = path.resolve(paths[i]);
        var fname = path.normalize(path.join(sp, name));
        if (fs.existsSync(fname)) {
            return fname;
        }
    }

    return null;
}

var zfsBin = findCmd('zfs');
function zfs(args, callback) {
    "use strict";

    cp.execFile(zfsBin, args, {maxBuffer: 8000000}, function (err, stdout, stderr) {
        if (callback && typeof callback === 'function') {
            if (err) {
                err.message = compact(err.message.split('\n')).join('; ').trim();
                callback(err);
            } else {
                callback(null, stdout);
            }
        }
    });
}

function parseNumber(str) {
    var m = str.match(/^([0-9.]+)([KMGT]?)$/);
    if (!m) {
        return -1;
    }

    var n = parseFloat(m[1]);
    if (m[2] === 'K') {
        n *= 1024;
    } else if (m[2] === 'M') {
        n *= 1024 * 1024;
    } else if (m[2] === 'G') {
        n *= 1024 * 1024 * 1024;
    } else if (m[2] === 'T') {
        n *= 1024 * 1024 * 1024 * 1024;
    } else if (m[2] === 'P') {
        n *= 1024 * 1024 * 1024 * 1024 * 1024;
    }

    return Math.floor(n);
}

function ZFS(info) {
    "use strict";

    var obj = Object.create({});
    if (typeof info === 'string') {
        info = info.split(/\s+/);
    }

    if (info.length !== 5) {
        return null;
    }

    obj.name = info[0];
    obj.used = parseNumber(info[1]);
    if (info[2] !== '-') { // Snapshots don't have 'avail' field.
        obj.avail = parseNumber(info[2]);
    }
    obj.refer = parseNumber(info[3]);
    obj.mountpoint = info[4];

    Object.freeze(obj);
    return obj;
}

function Property(info) {
    "use strict";

    var obj = Object.create({});
    if (typeof info === 'string') {
        info = info.split(/\s+/, 4);
    }

    if (info.length !== 4) {
        return null;
    }

    obj.name = info[0];
    obj.property = info[1];
    obj.value = info[2];
    obj.source = info[3];

    Object.freeze(obj);
    return obj;
}

function list(opts, cb) {
    "use strict";

    if (typeof opts === 'function') {
        cb = opts;
        opts = undefined;
    }

    var params = ['list', '-H'];

    if (opts && opts.type) {
        params.push('-t');
        params.push(opts.type);
    }

    zfs(params, function (err, stdout) {
        if (cb && typeof cb === 'function') {
            if (err) {
                cb(err);
                return;
            }
            var lines = compact(stdout.split('\n'));
            var list = lines.map(function (x) { return new ZFS(x); });
            cb(err, list);
        }
    });
}

function get(opts, cb) {
    "use strict";

    var params = [ 'get', '-pH' ];
    if (opts.source) {
        params.push('-s');
        params.push(opts.source);
    }
    params.push(opts.property);

    zfs(params, function (err, stdout) {
        if (cb && typeof cb === 'function') {
            var lines = compact(stdout.split('\n'));
            var list = lines.map(function (x) { return new Property(x); });
            cb(err, list);
        }
    });
}

function destroy(opts, cb) {
    "use strict";

    var params = [ 'destroy' ];
    if (opts.recursive) {
        params.push('-r');
    }
    params.push(opts.name);

    zfs(params, cb);
}

function create(opts, cb) {
    "use strict";

    var params = [ 'create' ];

    params.push(opts.name);

    zfs(params, cb);
}

function set(opts, cb) {
    "use strict";

    var params = [ 'set' ];

    params.push(opts.property + "=" + opts.value)

    params.push(opts.name);

    zfs(params, cb);
}

function snapshot(opts, cb) {
    "use strict";

    var params = [ 'snapshot' ];
    if (opts.recursive) {
        params.push('-r');
    }
    params.push(opts.dataset + '@' + opts.name);

    zfs(params, cb);
}

exports.get = get;
exports.set = set;
exports.list = list;
exports.destroy = destroy;
exports.create = create;
exports.snapshot = snapshot;
<|endoftext|>


<commit_before>'use strict';

var meta = module.parent.require('./meta');
var user = module.parent.require('./user');
var nconf = require('nconf');

var db = module.parent.require('./database'); // NodeBB / src / database 참고.
var au = module.parent.require('./controllers/authentication'); // NodeBB / src / controllers 참고.

var winston = require('winston');
// 모듈 객체.
var plugin = {};

plugin.init = function (params, callback)	{
	var router = params.router;

	winston.info('Start community..');

	router.get('/community', function ()	{
		console.log(arguments);
	});

	callback();
}

plugin.verifyUser = function (token, callback)	{

}

// 이 함수는 NodeBB 에서 어떤 동작 또는 페이지이동 때마다 호출되므로 계속해서 토큰을 받아오고 유저 유효성 검사를 할 것이다.
// 그러므로 함수 첫줄에 세션 확인을 하는 구문을 만들어 같은 세션일 경우 유저 유효성 검사를 넘어가도록 한다.
plugin.addMiddleware = function (req, res, next)	{

	db.init(function ()	{
		nconf.set('mongo:database', 'biobank-session');
	});
	console.log(nconf.get('mongo:database'));
	console.log('login add middle ware!!', req.headers.cookie);
	// 이미 있는 세션일 경우 요청 프로퍼티에 user 와 user 안에 uid 가 존재 한다.
	// TODO.
	// 로그아웃 후 창을 닫고 바이오클라우드에서 다시 커뮤니티로 접속하면 세션관련 리프레쉬가 발생한다.
	// 이 경우 세션이 변경됨을 확인하고 리프레쉬를 해줘야한다.
	var hasSession = req.hasOwnProperty('user') && req.user.hasOwnProperty('uid') && parseInt(req.user.uid, 10) > 10;

};
// 로그아웃 함수.
plugin.doLogout = function (data, callback)	{
	winston.info('Do logout..');

	if (typeof callback === 'function')	{
		callback();
	} else {
		return true;
	}
}

module.exports = plugin;<commit_msg>20170405140910<commit_after>'use strict';

var meta = module.parent.require('./meta');
var user = module.parent.require('./user');
var nconf = require('nconf');

var db = module.parent.require('./database'); // NodeBB / src / database 참고.
var au = module.parent.require('./controllers/authentication'); // NodeBB / src / controllers 참고.

var winston = require('winston');
// 모듈 객체.
var plugin = {};

plugin.init = function (params, callback)	{
	var router = params.router;

	winston.info('Start community..');

	router.get('/community', function ()	{
		console.log(arguments);
	});

	callback();
}

plugin.verifyUser = function (token, callback)	{

}

// 이 함수는 NodeBB 에서 어떤 동작 또는 페이지이동 때마다 호출되므로 계속해서 토큰을 받아오고 유저 유효성 검사를 할 것이다.
// 그러므로 함수 첫줄에 세션 확인을 하는 구문을 만들어 같은 세션일 경우 유저 유효성 검사를 넘어가도록 한다.
plugin.addMiddleware = function (req, res, next)	{
	nconf.set('mongo:database', 'biobank-session');
	console.log(nconf.get('mongo:database'));
	console.log('login add middle ware!!', req.headers.cookie);
	// 이미 있는 세션일 경우 요청 프로퍼티에 user 와 user 안에 uid 가 존재 한다.
	// TODO.
	// 로그아웃 후 창을 닫고 바이오클라우드에서 다시 커뮤니티로 접속하면 세션관련 리프레쉬가 발생한다.
	// 이 경우 세션이 변경됨을 확인하고 리프레쉬를 해줘야한다.
	var hasSession = req.hasOwnProperty('user') && req.user.hasOwnProperty('uid') && parseInt(req.user.uid, 10) > 10;

};
// 로그아웃 함수.
plugin.doLogout = function (data, callback)	{
	winston.info('Do logout..');

	if (typeof callback === 'function')	{
		callback();
	} else {
		return true;
	}
}

module.exports = plugin;<|endoftext|>


<commit_before>// Copyright (c) 2011 Hewlett-Packard Development Company, L.P. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Build and fly through a 3D world using Three.js

var WebVfx = WebVfx || {};

WebVfx.ThreeWorld = function (width, height, nearPlane, farPlane, animationData, readyCallback) {
    var self = this;

    this.tracker = new WebVfx.Tracker(function () {
        readyCallback();
        delete self.tracker;
    });
    this.jsonLoader = new THREE.JSONLoader();

    var aspect = width / height;
    this.camera = new WebVfx.AnimatedCamera(aspect, nearPlane, farPlane,
                                            animationData);
    this.scene = new THREE.Scene();
    // Blender OBJ and three.js exporters rotate scene by -PI/2 in X,
    // undo that here so our camera animation data works.
    this.scene.rotation.x = Math.PI / 2;
    this.scene.updateMatrix();

    this.widescreen = aspect > (4 / 3);

    // Create and size canvas ourself, more efficient to size it up front
    var canvas = document.createElement("canvas");
    canvas.width = width;
    canvas.height = height;
    this.renderer = new THREE.WebGLRenderer({ canvas: canvas,
                                              antialias: true });
    // Need to enable this for our shader
    if (!this.renderer.context.getExtension("OES_standard_derivatives"))
        console.log("OES_standard_derivatives not supported");
    this.renderer.setViewport(0, 0, width, height);
    document.body.appendChild(canvas);

    // Treat page load as trackable
    if (document.readyState != "complete") {
        this.tracker.increment();
        window.addEventListener('load', function (event) {
            self.tracker.decrement();
        }, false);
    }
};

WebVfx.ThreeWorld.prototype = {
    // Must be called to start resource tracking
    startTracking: function () {
        this.tracker.start();
    },

    // Optional _callback_ will be passed the Mesh for additional configuring
    addJSONModel: function (url, material, callback) {
        var self = this;
        this.tracker.increment();
        this.jsonLoader.load({ model: url, callback: function (geometry) {
            var mesh = new THREE.Mesh(geometry, material);
            if (callback)
                callback(mesh);
            self.scene.addObject(mesh);
            self.tracker.decrement();
        } });
    },

    resize: function (width, height) {
        this.renderer.setSize(width, height);
        this.camera.aspect = width / height;
        this.camera.updateProjectionMatrix();
        this.widescreen = this.camera.aspect > (4 / 3);
    },

    render: function (time) {
        this.camera.setAnimationTime(time);
        this.renderer.render(this.scene, this.camera);
    }
};

///////////

WebVfx.loadImageTexture = function (url, tracker) {
    var image = new Image();
    var texture = new THREE.Texture(image);
    WebVfx.trackTextureImage(texture, tracker);
    image.src = url;
    return texture;
};

WebVfx.trackTextureImage = function (texture, tracker) {
    var image = texture.image;
    if (!(image instanceof Image) || image.complete) {
        texture.needsUpdate = true;
        return;
    }
    tracker.increment();
    image.addEventListener("load", function () {
        texture.needsUpdate = true;
        tracker.decrement();
    });
};

///////////

// _options_ includes:
// fontStyle - e.g. "30px Helvetica". If it uses a font loaded with @font-face,
//   then you must use a WebVfx.FontTracker and create the text Texture after
//   the font has loaded.
// textColor - Text color css spec.
// backgroundColor - Background color css spec.
// textAlign - "left", "right" or "center".
// textBaseline - "top", "middle" or "bottom"
WebVfx.createTextTexture = function (text, targetWidth, targetHeight, options) {
    var fontStyle = options.fontStyle;
    var textColor = options.textColor;
    var backgroundColor = options.backgroundColor;
    var textAlign = options.textAlign;
    var textBaseline = options.textBaseline;

    // Canvas context provides measureText(), but this only exposes width.
    // So apply same font style to span and measure it.
    // FYI span text looks much better than canvas text because span uses
    // subpixel antialiasing
    var span = document.createElement("span");
    span.style.visibility = "hidden";
    span.style.font = fontStyle;
    span.textContent = text;
    document.body.appendChild(span);
    var textWidth = span.offsetWidth;
    var textHeight = span.offsetHeight;
    document.body.removeChild(span);

    var scale = Math.min(targetWidth / textWidth, targetHeight / textHeight);

    var canvas = document.createElement("canvas");
    canvas.width = targetWidth;
    canvas.height = targetHeight;

    var context = canvas.getContext("2d");
    if (backgroundColor) {
        context.fillStyle = backgroundColor;
        context.fillRect(0, 0, targetWidth, targetHeight);
    }
    // Scale text to fit if too large
    var positionScale = 1;
    if (scale < 1) {
        context.scale(scale, scale);
        positionScale = 1 / scale;
    }
    context.font = fontStyle;
    context.fillStyle = textColor;

    var x = 0, y = 0;

    switch (textAlign) {
    case "right":
        x = targetWidth;
        break;
    case "center":
        x = targetWidth / 2;
        break;
    case "left":
    default:
        textAlign = "left";
        break;
    }
    context.textAlign = textAlign;

    switch (textBaseline) {
    case "middle":
        y = targetHeight / 2;
        break;
    case "bottom":
        y = targetHeight;
        break;
    case "top":
    default:
        textBaseline = "top";
        break;
    }
    context.textBaseline = textBaseline;

    context.fillText(text, x * positionScale, y * positionScale);

    var texture = new THREE.Texture(canvas);
    texture.needsUpdate = true;
    return texture;
}

///////////

WebVfx.updateVideoTexture = function (texture, name) {
    texture.image = webvfx.getImage(name).toImageData();
    texture.needsUpdate = true;
};

///////////

// Texture mesh by multiplying two textures.
// Texture regions outside of UV space can be colored.
// Parameters:
//  texture1 - THREE.Texture - uses main UV
//  texture2 - (optional) THREE.Texture - uses secondary UV
//  borderColor - (optional) THREE.Color - color of regions outside UV space.
//   Setting this also triggers antialiasing of the edges.
//  borderOpacity - float opacity of regions outside UV space (default 1.0)
WebVfx.MeshMultitextureMaterial = function (parameters) {
    var vertexShader = this.shaderLibrary['vertexShader'];
    var fragmentShader = this.shaderLibrary['fragmentShader'];
    var vertexPrefix = [];
    var fragmentPrefix = [];

    var uniforms = THREE.UniformsUtils.merge([THREE.UniformsLib["shadowmap"]]);
    uniforms.texture1 = { type: "t", value: 0, texture: parameters.texture1 };

    if (parameters.texture2) {
        uniforms.texture2 = { type: "t", value: 1, texture: parameters.texture2 };
        vertexPrefix.push("#define USE_TEXTURE2");
        fragmentPrefix.push("#define USE_TEXTURE2");
    }
    if (parameters.borderColor) {
        uniforms.borderColor = { type: "c", value: parameters.borderColor };
        uniforms.borderOpacity = { type: "f", value: parameters.borderOpacity || 1.0 };
        fragmentPrefix.push("#define USE_BORDERCOLOR");
    }

    if (vertexPrefix.length > 0)
        vertexShader = vertexPrefix.join("\n") + vertexShader;
    if (fragmentPrefix.length > 0)
        fragmentShader = fragmentPrefix.join("\n") + fragmentShader;

    THREE.MeshShaderMaterial.call(this, {
        uniforms: uniforms,
        fragmentShader: fragmentShader,
        vertexShader: vertexShader
    });
};

WebVfx.MeshMultitextureMaterial.prototype = new THREE.MeshShaderMaterial();
WebVfx.MeshMultitextureMaterial.prototype.constructor = WebVfx.MeshMultitextureMaterial;

WebVfx.MeshMultitextureMaterial.prototype.shaderLibrary = {
    vertexShader: [
        "",
        "varying vec2 vUv;",
        "#ifdef USE_TEXTURE2",
        "varying vec2 vUv2;",
        "#endif",
        THREE.ShaderChunk["shadowmap_pars_vertex"],
        "void main() {",
        "    vUv = uv;",
        "#ifdef USE_TEXTURE2",
        "    vUv2 = uv2;",
        "#endif",
        "    vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);",
        "    gl_Position = projectionMatrix * mvPosition;",
             THREE.ShaderChunk["shadowmap_vertex"],
        "}"
    ].join("\n"),
    fragmentShader: [
        "",
        "varying vec2 vUv;",
        "uniform sampler2D texture1;",

        "#ifdef USE_TEXTURE2",
        "varying vec2 vUv2;",
        "uniform sampler2D texture2;",
        "#endif",

        "#ifdef USE_BORDERCOLOR",
        "#extension GL_OES_standard_derivatives : enable",
        "uniform vec3 borderColor;",
        "uniform float borderOpacity;",
        "#endif",

        THREE.ShaderChunk["shadowmap_pars_fragment"],

        "#ifdef USE_BORDERCOLOR",
        "vec4 antialias(vec2 uv, vec2 fw, sampler2D texture, vec4 color) {",
        "    vec2 marginTL = -fw;",
        "    vec2 marginBR = 1.0 + fw;",
        //   // Completely outside the texture margins, use color
        "    if (uv.s <= marginTL.s || uv.s >= marginBR.s",
        "        || uv.t <= marginTL.t || uv.t >= marginBR.t)",
        "        return color;",
        //   // Completely inside the texture, use texture
        "    else if (uv.s >= 0.0 && uv.s <= 1.0 && uv.t >= 0.0 && uv.t <= 1.0)",
        "        return texture2D(texture, uv);",
        //   // Inside a margin - mix color and texture to antialias
        "    else {",
        //       // Amount of mix - 0.0 is all color, 1.0 is all texture
        "        float p = 0.0;",
        "        int c = 0;",
        "        if (uv.s < 0.0) {",
        "            p += smoothstep(marginTL.s, 0.0, uv.s);",
        "            c++;",
        "        }",
        "        else if (uv.s > 1.0) {",
        "            p += 1.0 - smoothstep(1.0, marginBR.s, uv.s);",
        "            c++;",
        "        }",
        "        if (uv.t < 0.0) {",
        "            p += smoothstep(marginTL.t, 0.0, uv.t);",
        "            c++;",
        "        }",
        "        else if (uv.t > 1.0) {",
        "            p += 1.0 - smoothstep(1.0, marginBR.t, uv.t);",
        "            c++;",
        "        }",
        "        return mix(color, texture2D(texture, uv), p / float(c));",
        "    }",
        "}",
        "#endif",

        "void main() {",
        "#ifdef USE_BORDERCOLOR",
        "    vec4 border = vec4(borderColor, borderOpacity);",
        "    gl_FragColor = antialias(vUv, fwidth(vUv), texture1, border);",
        "#else",
        "    gl_FragColor = texture2D(texture1, vUv);",
        "#endif",

        "#ifdef USE_TEXTURE2",
        "#ifdef USE_BORDERCOLOR",
        "    gl_FragColor = gl_FragColor * antialias(vUv2, fwidth(vUv2), texture2, border);",
        "#else",
        "    gl_FragColor = gl_FragColor * texture2D(texture2, vUv2);",
        "#endif",
        "#endif",

            THREE.ShaderChunk["shadowmap_fragment"],
        "}"
    ].join("\n")
};

///////////

// Set UV coords for given layer, for a single quad
WebVfx.setQuadUvs = function (mesh, layer, u1, v1, u2, v2, u3, v3, u4, v4) {
    var uvs = [new THREE.UV(u1, v1),
               new THREE.UV(u2, v2),
               new THREE.UV(u3, v3),
               new THREE.UV(u4, v4)];
    mesh.geometry.faceVertexUvs[layer] = [uvs];
}

// Map (scale/translate) UVs from _fromLayer_ to _toLayer_
// min/max specifies new UV coordinate space range.
WebVfx.mapUvs = function (mesh, fromLayer, toLayer, minU, minV, maxU, maxV) {
    var uvs = mesh.geometry.faceVertexUvs[fromLayer];
    var length = uvs.length;

    // Find UV space we are mapping from
    var fromMinU = Number.MAX_VALUE;
    var fromMaxU = Number.MIN_VALUE;
    var fromMinV = Number.MAX_VALUE;
    var fromMaxV = Number.MIN_VALUE;
    for (var i = 0; i < length; i++) {
        var faceUv = uvs[i];
        var faceUvLength = faceUv.length;
        for (var j = 0; j < faceUvLength; j++) {
            var uv = faceUv[j];
            fromMinU = Math.min(uv.u, fromMinU);
            fromMaxU = Math.max(uv.u, fromMaxU);
            fromMinV = Math.min(uv.v, fromMinV);
            fromMaxV = Math.max(uv.v, fromMaxV);
        }
    }

    var fromDistU = fromMaxU - fromMinU;
    var fromDistV = fromMaxV - fromMinV;
    var toDistU = maxU - minU;
    var toDistV = maxV - minV;

    var mappedUvs = new Array(length);
    for (var i = 0; i < length; i++) {
        var faceUv = uvs[i];
        var faceUvLength = faceUv.length;
        var mappedFaceUv = new Array(faceUvLength);
        mappedUvs[i] = mappedFaceUv;
        for (var j = 0; j < faceUvLength; j++) {
            var uv = faceUv[j];
            var u = minU + (((uv.u - fromMinU) / fromDistU) * toDistU);
            var v = minV + (((uv.v - fromMinV) / fromDistV) * toDistV);
            mappedFaceUv[j] = new THREE.UV(u, v);
        }
    }

    mesh.geometry.faceVertexUvs[toLayer] = mappedUvs;
}

///////////

WebVfx.AnimatedCamera = function (aspect, nearPlane, farPlane, animationData) {
    var anim = new WebVfx.Animation(animationData);
    this.animation = anim;
    var fov = anim.radians2degrees(anim.verticalFOV(aspect));
    THREE.Camera.call(this, fov, aspect, nearPlane, farPlane);
    this.useTarget = false;
    // Blender XYZ order is really ZYX
    this.eulerOrder = 'ZYX';
};

WebVfx.AnimatedCamera.prototype = new THREE.Camera();
WebVfx.AnimatedCamera.prototype.constructor = WebVfx.AnimatedCamera;

WebVfx.AnimatedCamera.prototype.setAnimationTime = function (time) {
    var anim = this.animation;
    anim.evaluateTime(time);
    this.position.x = anim.locationX;
    this.position.y = anim.locationY;
    this.position.z = anim.locationZ;
    this.rotation.x = anim.rotationX;
    this.rotation.y = anim.rotationY;
    this.rotation.z = anim.rotationZ;
};

<commit_msg>Update for Three.js r45<commit_after>// Copyright (c) 2011 Hewlett-Packard Development Company, L.P. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Build and fly through a 3D world using Three.js

var WebVfx = WebVfx || {};

WebVfx.ThreeWorld = function (width, height, nearPlane, farPlane, animationData, readyCallback) {
    var self = this;

    this.tracker = new WebVfx.Tracker(function () {
        readyCallback();
        delete self.tracker;
    });
    this.jsonLoader = new THREE.JSONLoader();

    var aspect = width / height;
    this.camera = new WebVfx.AnimatedCamera(aspect, nearPlane, farPlane,
                                            animationData);
    this.scene = new THREE.Scene();
    // Blender OBJ and three.js exporters rotate scene by -PI/2 in X,
    // undo that here so our camera animation data works.
    this.scene.rotation.x = Math.PI / 2;
    this.scene.updateMatrix();

    this.widescreen = aspect > (4 / 3);

    // Create and size canvas ourself, more efficient to size it up front
    var canvas = document.createElement("canvas");
    canvas.width = width;
    canvas.height = height;
    this.renderer = new THREE.WebGLRenderer({ canvas: canvas,
                                              antialias: true });
    // Need to enable this for our shader
    if (!this.renderer.context.getExtension("OES_standard_derivatives"))
        console.log("OES_standard_derivatives not supported");
    this.renderer.setViewport(0, 0, width, height);
    document.body.appendChild(canvas);

    // Treat page load as trackable
    if (document.readyState != "complete") {
        this.tracker.increment();
        window.addEventListener('load', function (event) {
            self.tracker.decrement();
        }, false);
    }
};

WebVfx.ThreeWorld.prototype = {
    // Must be called to start resource tracking
    startTracking: function () {
        this.tracker.start();
    },

    // Optional _callback_ will be passed the Mesh for additional configuring
    addJSONModel: function (url, material, callback) {
        var self = this;
        this.tracker.increment();
        this.jsonLoader.load({ model: url, callback: function (geometry) {
            var mesh = new THREE.Mesh(geometry, material);
            if (callback)
                callback(mesh);
            self.scene.add(mesh);
            self.tracker.decrement();
        } });
    },

    resize: function (width, height) {
        this.renderer.setSize(width, height);
        this.camera.aspect = width / height;
        this.camera.updateProjectionMatrix();
        this.widescreen = this.camera.aspect > (4 / 3);
    },

    render: function (time) {
        this.camera.setAnimationTime(time);
        this.renderer.render(this.scene, this.camera);
    }
};

///////////

WebVfx.loadImageTexture = function (url, tracker) {
    var image = new Image();
    var texture = new THREE.Texture(image);
    WebVfx.trackTextureImage(texture, tracker);
    image.src = url;
    return texture;
};

WebVfx.trackTextureImage = function (texture, tracker) {
    var image = texture.image;
    if (!(image instanceof Image) || image.complete) {
        texture.needsUpdate = true;
        return;
    }
    tracker.increment();
    image.addEventListener("load", function () {
        texture.needsUpdate = true;
        tracker.decrement();
    });
};

///////////

// _options_ includes:
// fontStyle - e.g. "30px Helvetica". If it uses a font loaded with @font-face,
//   then you must use a WebVfx.FontTracker and create the text Texture after
//   the font has loaded.
// textColor - Text color css spec.
// backgroundColor - Background color css spec.
// textAlign - "left", "right" or "center".
// textBaseline - "top", "middle" or "bottom"
WebVfx.createTextTexture = function (text, targetWidth, targetHeight, options) {
    var fontStyle = options.fontStyle;
    var textColor = options.textColor;
    var backgroundColor = options.backgroundColor;
    var textAlign = options.textAlign;
    var textBaseline = options.textBaseline;

    // Canvas context provides measureText(), but this only exposes width.
    // So apply same font style to span and measure it.
    // FYI span text looks much better than canvas text because span uses
    // subpixel antialiasing
    var span = document.createElement("span");
    span.style.visibility = "hidden";
    span.style.font = fontStyle;
    span.textContent = text;
    document.body.appendChild(span);
    var textWidth = span.offsetWidth;
    var textHeight = span.offsetHeight;
    document.body.removeChild(span);

    var scale = Math.min(targetWidth / textWidth, targetHeight / textHeight);

    var canvas = document.createElement("canvas");
    canvas.width = targetWidth;
    canvas.height = targetHeight;

    var context = canvas.getContext("2d");
    if (backgroundColor) {
        context.fillStyle = backgroundColor;
        context.fillRect(0, 0, targetWidth, targetHeight);
    }
    // Scale text to fit if too large
    var positionScale = 1;
    if (scale < 1) {
        context.scale(scale, scale);
        positionScale = 1 / scale;
    }
    context.font = fontStyle;
    context.fillStyle = textColor;

    var x = 0, y = 0;

    switch (textAlign) {
    case "right":
        x = targetWidth;
        break;
    case "center":
        x = targetWidth / 2;
        break;
    case "left":
    default:
        textAlign = "left";
        break;
    }
    context.textAlign = textAlign;

    switch (textBaseline) {
    case "middle":
        y = targetHeight / 2;
        break;
    case "bottom":
        y = targetHeight;
        break;
    case "top":
    default:
        textBaseline = "top";
        break;
    }
    context.textBaseline = textBaseline;

    context.fillText(text, x * positionScale, y * positionScale);

    var texture = new THREE.Texture(canvas);
    texture.needsUpdate = true;
    return texture;
}

///////////

WebVfx.updateVideoTexture = function (texture, name) {
    texture.image = webvfx.getImage(name).toImageData();
    texture.needsUpdate = true;
};

///////////

// Texture mesh by multiplying two textures.
// Texture regions outside of UV space can be colored.
// Parameters:
//  texture1 - THREE.Texture - uses main UV
//  texture2 - (optional) THREE.Texture - uses secondary UV
//  borderColor - (optional) THREE.Color - color of regions outside UV space.
//   Setting this also triggers antialiasing of the edges.
//  borderOpacity - float opacity of regions outside UV space (default 1.0)
WebVfx.MultitextureShaderMaterial = function (parameters) {
    var vertexShader = this.shaderLibrary['vertexShader'];
    var fragmentShader = this.shaderLibrary['fragmentShader'];
    var vertexPrefix = [];
    var fragmentPrefix = [];

    var uniforms = THREE.UniformsUtils.merge([THREE.UniformsLib["shadowmap"]]);
    uniforms.texture1 = { type: "t", value: 0, texture: parameters.texture1 };

    if (parameters.texture2) {
        uniforms.texture2 = { type: "t", value: 1, texture: parameters.texture2 };
        vertexPrefix.push("#define USE_TEXTURE2");
        fragmentPrefix.push("#define USE_TEXTURE2");
    }
    if (parameters.borderColor) {
        uniforms.borderColor = { type: "c", value: parameters.borderColor };
        uniforms.borderOpacity = { type: "f", value: parameters.borderOpacity || 1.0 };
        fragmentPrefix.push("#define USE_BORDERCOLOR");
    }

    if (vertexPrefix.length > 0)
        vertexShader = vertexPrefix.join("\n") + vertexShader;
    if (fragmentPrefix.length > 0)
        fragmentShader = fragmentPrefix.join("\n") + fragmentShader;

    THREE.ShaderMaterial.call(this, {
        uniforms: uniforms,
        fragmentShader: fragmentShader,
        vertexShader: vertexShader
    });
};

WebVfx.MultitextureShaderMaterial.prototype = new THREE.ShaderMaterial();
WebVfx.MultitextureShaderMaterial.prototype.constructor = WebVfx.MultitextureShaderMaterial;

WebVfx.MultitextureShaderMaterial.prototype.shaderLibrary = {
    vertexShader: [
        "",
        "varying vec2 vUv;",
        "#ifdef USE_TEXTURE2",
        "varying vec2 vUv2;",
        "#endif",
        THREE.ShaderChunk["shadowmap_pars_vertex"],
        "void main() {",
        "    vUv = uv;",
        "#ifdef USE_TEXTURE2",
        "    vUv2 = uv2;",
        "#endif",
        "    vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);",
        "    gl_Position = projectionMatrix * mvPosition;",
             THREE.ShaderChunk["shadowmap_vertex"],
        "}"
    ].join("\n"),
    fragmentShader: [
        "",
        "varying vec2 vUv;",
        "uniform sampler2D texture1;",

        "#ifdef USE_TEXTURE2",
        "varying vec2 vUv2;",
        "uniform sampler2D texture2;",
        "#endif",

        "#ifdef USE_BORDERCOLOR",
        "#extension GL_OES_standard_derivatives : enable",
        "uniform vec3 borderColor;",
        "uniform float borderOpacity;",
        "#endif",

        THREE.ShaderChunk["shadowmap_pars_fragment"],

        "#ifdef USE_BORDERCOLOR",
        "vec4 antialias(vec2 uv, vec2 fw, sampler2D texture, vec4 color) {",
        "    vec2 marginTL = -fw;",
        "    vec2 marginBR = 1.0 + fw;",
        //   // Completely outside the texture margins, use color
        "    if (uv.s <= marginTL.s || uv.s >= marginBR.s",
        "        || uv.t <= marginTL.t || uv.t >= marginBR.t)",
        "        return color;",
        //   // Completely inside the texture, use texture
        "    else if (uv.s >= 0.0 && uv.s <= 1.0 && uv.t >= 0.0 && uv.t <= 1.0)",
        "        return texture2D(texture, uv);",
        //   // Inside a margin - mix color and texture to antialias
        "    else {",
        //       // Amount of mix - 0.0 is all color, 1.0 is all texture
        "        float p = 0.0;",
        "        int c = 0;",
        "        if (uv.s < 0.0) {",
        "            p += smoothstep(marginTL.s, 0.0, uv.s);",
        "            c++;",
        "        }",
        "        else if (uv.s > 1.0) {",
        "            p += 1.0 - smoothstep(1.0, marginBR.s, uv.s);",
        "            c++;",
        "        }",
        "        if (uv.t < 0.0) {",
        "            p += smoothstep(marginTL.t, 0.0, uv.t);",
        "            c++;",
        "        }",
        "        else if (uv.t > 1.0) {",
        "            p += 1.0 - smoothstep(1.0, marginBR.t, uv.t);",
        "            c++;",
        "        }",
        "        return mix(color, texture2D(texture, uv), p / float(c));",
        "    }",
        "}",
        "#endif",

        "void main() {",
        "#ifdef USE_BORDERCOLOR",
        "    vec4 border = vec4(borderColor, borderOpacity);",
        "    gl_FragColor = antialias(vUv, fwidth(vUv), texture1, border);",
        "#else",
        "    gl_FragColor = texture2D(texture1, vUv);",
        "#endif",

        "#ifdef USE_TEXTURE2",
        "#ifdef USE_BORDERCOLOR",
        "    gl_FragColor = gl_FragColor * antialias(vUv2, fwidth(vUv2), texture2, border);",
        "#else",
        "    gl_FragColor = gl_FragColor * texture2D(texture2, vUv2);",
        "#endif",
        "#endif",

            THREE.ShaderChunk["shadowmap_fragment"],
        "}"
    ].join("\n")
};

///////////

// Set UV coords for given layer, for a single quad
WebVfx.setQuadUvs = function (mesh, layer, u1, v1, u2, v2, u3, v3, u4, v4) {
    var uvs = [new THREE.UV(u1, v1),
               new THREE.UV(u2, v2),
               new THREE.UV(u3, v3),
               new THREE.UV(u4, v4)];
    mesh.geometry.faceVertexUvs[layer] = [uvs];
}

// Map (scale/translate) UVs from _fromLayer_ to _toLayer_
// min/max specifies new UV coordinate space range.
WebVfx.mapUvs = function (mesh, fromLayer, toLayer, minU, minV, maxU, maxV) {
    var uvs = mesh.geometry.faceVertexUvs[fromLayer];
    var length = uvs.length;

    // Find UV space we are mapping from
    var fromMinU = Number.MAX_VALUE;
    var fromMaxU = Number.MIN_VALUE;
    var fromMinV = Number.MAX_VALUE;
    var fromMaxV = Number.MIN_VALUE;
    for (var i = 0; i < length; i++) {
        var faceUv = uvs[i];
        var faceUvLength = faceUv.length;
        for (var j = 0; j < faceUvLength; j++) {
            var uv = faceUv[j];
            fromMinU = Math.min(uv.u, fromMinU);
            fromMaxU = Math.max(uv.u, fromMaxU);
            fromMinV = Math.min(uv.v, fromMinV);
            fromMaxV = Math.max(uv.v, fromMaxV);
        }
    }

    var fromDistU = fromMaxU - fromMinU;
    var fromDistV = fromMaxV - fromMinV;
    var toDistU = maxU - minU;
    var toDistV = maxV - minV;

    var mappedUvs = new Array(length);
    for (var i = 0; i < length; i++) {
        var faceUv = uvs[i];
        var faceUvLength = faceUv.length;
        var mappedFaceUv = new Array(faceUvLength);
        mappedUvs[i] = mappedFaceUv;
        for (var j = 0; j < faceUvLength; j++) {
            var uv = faceUv[j];
            var u = minU + (((uv.u - fromMinU) / fromDistU) * toDistU);
            var v = minV + (((uv.v - fromMinV) / fromDistV) * toDistV);
            mappedFaceUv[j] = new THREE.UV(u, v);
        }
    }

    mesh.geometry.faceVertexUvs[toLayer] = mappedUvs;
}

///////////

WebVfx.AnimatedCamera = function (aspect, nearPlane, farPlane, animationData) {
    var anim = new WebVfx.Animation(animationData);
    this.animation = anim;
    var fov = anim.radians2degrees(anim.verticalFOV(aspect));
    THREE.PerspectiveCamera.call(this, fov, aspect, nearPlane, farPlane);
    this.useTarget = false;
    // Blender XYZ order is really ZYX
    this.eulerOrder = 'ZYX';
};

WebVfx.AnimatedCamera.prototype = new THREE.PerspectiveCamera();
WebVfx.AnimatedCamera.prototype.constructor = WebVfx.AnimatedCamera;

WebVfx.AnimatedCamera.prototype.setAnimationTime = function (time) {
    var anim = this.animation;
    anim.evaluateTime(time);
    this.position.x = anim.locationX;
    this.position.y = anim.locationY;
    this.position.z = anim.locationZ;
    this.rotation.x = anim.rotationX;
    this.rotation.y = anim.rotationY;
    this.rotation.z = anim.rotationZ;
};

<|endoftext|>


<commit_before>'use strict';

var	async = module.parent.require('async'),
	XRegExp = module.parent.require('xregexp').XRegExp,
	validator = module.parent.require('validator'),

	nconf = module.parent.require('nconf'),
	Topics = module.parent.require('./topics'),
	User = module.parent.require('./user'),
	Groups = module.parent.require('./groups'),
	Notifications = module.parent.require('./notifications'),
	Privileges = module.parent.require('./privileges'),
	Utils = module.parent.require('../public/src/utils'),

	SocketPlugins = module.parent.require('./socket.io/plugins'),

	regex = XRegExp('(@[\\p{L}\\d\\-_.]+)', 'g'),
	isLatinMention = /@[\w\d\-_.]+$/,
	removePunctuationSuffix = function(string) {
		return string.replace(/[!?.]*$/, '');
	},

	Mentions = {};

SocketPlugins.mentions = {};

Mentions.notify = function(postData) {
	function filter(matches, method, callback) {
		async.filter(matches, function(match, next) {
			method(match, function(err, exists) {
				next(!err && exists);
			});
		}, function(matches) {
			callback(null, matches);
		});
	}

	var cleanedContent = Mentions.clean(postData.content, true, true, true);
	var matches = cleanedContent.match(regex);

	if (!matches) {
		return;
	}

	var noMentionGroups = ['registered-users', 'guests'];

	matches = matches.map(function(match) {
		return Utils.slugify(match.slice(1));
	}).filter(function(match, index, array) {
		return match && array.indexOf(match) === index && noMentionGroups.indexOf(match) === -1;
	});

	async.parallel({
		userRecipients: function(next) {
			filter(matches, User.exists, next);
		},
		groupRecipients: function(next) {
			filter(matches, Groups.exists, next);
		}
	}, function(err, results) {
		if (err) {
			return;
		}

		async.parallel({
			topic: function(next) {
				Topics.getTopicFields(postData.tid, ['title', 'cid'], next);
			},
			author: function(next) {
				User.getUserField(postData.uid, 'username', next);
			},
			uids: function(next) {
				async.map(results.userRecipients, function(slug, next) {
					User.getUidByUserslug(slug, next);
				}, next);
			},
			groupsMembers: function(next) {
				getGroupMemberUids(results.groupRecipients, next);
			}
		}, function(err, results) {
			if (err) {
				return;
			}

			var uids = results.uids.concat(results.groupsMembers).filter(function(uid, index, array) {
				return array.indexOf(uid) === index && parseInt(uid, 10) !== parseInt(postData.uid, 10);
			});

			if (!uids.length) {
				return;
			}

			Privileges.categories.filterUids('read', results.topic.cid, uids, function(err, uids) {
				if (err || !uids.length) {
					return;
				}

				Notifications.create({
					bodyShort: '[[notifications:user_mentioned_you_in, ' + results.author + ', ' + results.topic.title + ']]',
					bodyLong: postData.content,
					nid: 'tid:' + postData.tid + ':pid:' + postData.pid + ':uid:' + postData.uid,
					pid: postData.pid,
					tid: postData.tid,
					from: postData.uid,
					importance: 6
				}, function(err, notification) {
					if (err || !notification) {
						return;
					}
					Notifications.push(notification, uids);
				});
			});
		});
	});
};

function getGroupMemberUids(groupRecipients, callback) {
	async.map(groupRecipients, function(slug, next) {
		Groups.getGroupNameByGroupSlug(slug, next);
	}, function(err, groups) {
		if (err) {
			return callback(err);
		}
		async.map(groups, function(group, next) {
			Groups.getMembers(group, 0, -1, next);
		}, function(err, results) {
			if (err) {
				return callback(err);
			}

			var uids = [];
			results.forEach(function(members) {
				uids = uids.concat(members);
			});
			uids = uids.filter(function(uid, index, array) {
				return parseInt(uid, 10) && array.indexOf(uid) === index;
			});
			callback(null, uids);
		});
	});
}

Mentions.parsePost = function(data, callback) {
	if (!data || !data.postData || !data.postData.content) {
		return callback(null, data);
	}

	Mentions.parseRaw(data.postData.content, function(err, content) {
		if (err) {
			return callback(err);
		}

		data.postData.content = content;
		callback(null, data);
	});
};

Mentions.parseRaw = function(content, callback) {
	var cleanedContent = Mentions.clean(content, false, false, true);

	var matches = cleanedContent.match(regex);

	if (!matches) {
		return callback(null, content);
	}

	// Eliminate duplicates
	matches = matches.filter(function(cur, idx) {
		return idx === matches.indexOf(cur);
	});

	async.each(matches, function(match, next) {
		var slug = Utils.slugify(match.slice(1));

		match = removePunctuationSuffix(match);

		async.parallel({
			groupExists: async.apply(Groups.existsBySlug, slug),
			uid: async.apply(User.getUidByUserslug, slug)
		}, function(err, results) {
			if (err) {
				return next(err);
			}

			if (results.uid || results.groupExists) {
				var regex = isLatinMention.test(match)
					? new RegExp(match + '\\b', 'g')
					: new RegExp(match, 'g');

				var str = results.uid
					? '' + match + ''
					: '' + match + '';

				content = content.replace(regex, str);
			}

			next();
		});
	}, function(err) {
		callback(err, content);
	});
};

Mentions.clean = function(input, isMarkdown, stripBlockquote, stripCode) {
	if (stripBlockquote) {
		var bqMatch = isMarkdown ? /^>.*$/gm : /^
.*<\/blockquote>/gm;
		input = input.replace(bqMatch, '');
	}
	if (stripCode) {
		var pfMatch = isMarkdown ? /`[^`\n]+`/gm : /.*<\/code>/gm;
		input = input.replace(pfMatch, '');
	}

	return input;
};

/*
	WebSocket methods
*/

SocketPlugins.mentions.listGroups = function(socket, data, callback) {
	Groups.getGroups('groups:visible:createtime', 0, -1, function(err, groups) {
		if (err) {
			return callback(err);
		}
		groups = groups.filter(Boolean).map(function(groupName) {
			return validator.escape(groupName);
		});
		callback(null, groups);
	});
};

module.exports = Mentions;
<commit_msg>prevent crash if input is falsy<commit_after>'use strict';

var	async = module.parent.require('async'),
	XRegExp = module.parent.require('xregexp').XRegExp,
	validator = module.parent.require('validator'),

	nconf = module.parent.require('nconf'),
	Topics = module.parent.require('./topics'),
	User = module.parent.require('./user'),
	Groups = module.parent.require('./groups'),
	Notifications = module.parent.require('./notifications'),
	Privileges = module.parent.require('./privileges'),
	Utils = module.parent.require('../public/src/utils'),

	SocketPlugins = module.parent.require('./socket.io/plugins'),

	regex = XRegExp('(@[\\p{L}\\d\\-_.]+)', 'g'),
	isLatinMention = /@[\w\d\-_.]+$/,
	removePunctuationSuffix = function(string) {
		return string.replace(/[!?.]*$/, '');
	},

	Mentions = {};

SocketPlugins.mentions = {};

Mentions.notify = function(postData) {
	function filter(matches, method, callback) {
		async.filter(matches, function(match, next) {
			method(match, function(err, exists) {
				next(!err && exists);
			});
		}, function(matches) {
			callback(null, matches);
		});
	}

	var cleanedContent = Mentions.clean(postData.content, true, true, true);
	var matches = cleanedContent.match(regex);

	if (!matches) {
		return;
	}

	var noMentionGroups = ['registered-users', 'guests'];

	matches = matches.map(function(match) {
		return Utils.slugify(match.slice(1));
	}).filter(function(match, index, array) {
		return match && array.indexOf(match) === index && noMentionGroups.indexOf(match) === -1;
	});

	async.parallel({
		userRecipients: function(next) {
			filter(matches, User.exists, next);
		},
		groupRecipients: function(next) {
			filter(matches, Groups.exists, next);
		}
	}, function(err, results) {
		if (err) {
			return;
		}

		async.parallel({
			topic: function(next) {
				Topics.getTopicFields(postData.tid, ['title', 'cid'], next);
			},
			author: function(next) {
				User.getUserField(postData.uid, 'username', next);
			},
			uids: function(next) {
				async.map(results.userRecipients, function(slug, next) {
					User.getUidByUserslug(slug, next);
				}, next);
			},
			groupsMembers: function(next) {
				getGroupMemberUids(results.groupRecipients, next);
			}
		}, function(err, results) {
			if (err) {
				return;
			}

			var uids = results.uids.concat(results.groupsMembers).filter(function(uid, index, array) {
				return array.indexOf(uid) === index && parseInt(uid, 10) !== parseInt(postData.uid, 10);
			});

			if (!uids.length) {
				return;
			}

			Privileges.categories.filterUids('read', results.topic.cid, uids, function(err, uids) {
				if (err || !uids.length) {
					return;
				}

				Notifications.create({
					bodyShort: '[[notifications:user_mentioned_you_in, ' + results.author + ', ' + results.topic.title + ']]',
					bodyLong: postData.content,
					nid: 'tid:' + postData.tid + ':pid:' + postData.pid + ':uid:' + postData.uid,
					pid: postData.pid,
					tid: postData.tid,
					from: postData.uid,
					importance: 6
				}, function(err, notification) {
					if (err || !notification) {
						return;
					}
					Notifications.push(notification, uids);
				});
			});
		});
	});
};

function getGroupMemberUids(groupRecipients, callback) {
	async.map(groupRecipients, function(slug, next) {
		Groups.getGroupNameByGroupSlug(slug, next);
	}, function(err, groups) {
		if (err) {
			return callback(err);
		}
		async.map(groups, function(group, next) {
			Groups.getMembers(group, 0, -1, next);
		}, function(err, results) {
			if (err) {
				return callback(err);
			}

			var uids = [];
			results.forEach(function(members) {
				uids = uids.concat(members);
			});
			uids = uids.filter(function(uid, index, array) {
				return parseInt(uid, 10) && array.indexOf(uid) === index;
			});
			callback(null, uids);
		});
	});
}

Mentions.parsePost = function(data, callback) {
	if (!data || !data.postData || !data.postData.content) {
		return callback(null, data);
	}

	Mentions.parseRaw(data.postData.content, function(err, content) {
		if (err) {
			return callback(err);
		}

		data.postData.content = content;
		callback(null, data);
	});
};

Mentions.parseRaw = function(content, callback) {
	var cleanedContent = Mentions.clean(content, false, false, true);

	var matches = cleanedContent.match(regex);

	if (!matches) {
		return callback(null, content);
	}

	// Eliminate duplicates
	matches = matches.filter(function(cur, idx) {
		return idx === matches.indexOf(cur);
	});

	async.each(matches, function(match, next) {
		var slug = Utils.slugify(match.slice(1));

		match = removePunctuationSuffix(match);

		async.parallel({
			groupExists: async.apply(Groups.existsBySlug, slug),
			uid: async.apply(User.getUidByUserslug, slug)
		}, function(err, results) {
			if (err) {
				return next(err);
			}

			if (results.uid || results.groupExists) {
				var regex = isLatinMention.test(match)
					? new RegExp(match + '\\b', 'g')
					: new RegExp(match, 'g');

				var str = results.uid
					? '' + match + ''
					: '' + match + '';

				content = content.replace(regex, str);
			}

			next();
		});
	}, function(err) {
		callback(err, content);
	});
};

Mentions.clean = function(input, isMarkdown, stripBlockquote, stripCode) {
	if (!input) {
		return input;
	}
	if (stripBlockquote) {
		var bqMatch = isMarkdown ? /^>.*$/gm : /^
.*<\/blockquote>/gm;
		input = input.replace(bqMatch, '');
	}
	if (stripCode) {
		var pfMatch = isMarkdown ? /`[^`\n]+`/gm : /.*<\/code>/gm;
		input = input.replace(pfMatch, '');
	}

	return input;
};

/*
	WebSocket methods
*/

SocketPlugins.mentions.listGroups = function(socket, data, callback) {
	Groups.getGroups('groups:visible:createtime', 0, -1, function(err, groups) {
		if (err) {
			return callback(err);
		}
		groups = groups.filter(Boolean).map(function(groupName) {
			return validator.escape(groupName);
		});
		callback(null, groups);
	});
};

module.exports = Mentions;
<|endoftext|>