<commit_before>/**
 * Module dependencies.
 */

var express = require('express')
  , fs = require('fs')
  , path = require('path')
  , utils = require('./utils')
  , async = require('async')
  , imgr = require('./constants');

/**
 * Default options.
 */

var default_options = {
    namespace: '/'
  , cache_dir: '/tmp/imgr'
  , url_rewrite: '/:path/:size/:file.:ext'
  , whitelist: false
  , blacklist: false
  , debug: false
  , as_route: false
  , querystring_301: true
  , try_content: true
  , try_cache: true
  , trace: function () {}
};

/**
 * Create a new static image server.
 *
 * @param {String} path - where to serve images from
 * @param {Object} options (optional)
 */

var Server = exports.Server = function (path, options, imgr) {
    this.path = path;
    this.imgr = imgr;
    this.options = utils.mergeDefaults(options, default_options);
};

/**
 * Set the image namespace.
 *
 * @param {String} namespace - e.g. /images
 * @return this
 */

Server.prototype.namespace = function (namespace) {
    this.options.namespace = namespace || '/';
    return this;
};

/**
 * Set the cached/compiled image directory.
 *
 * @param {String} path
 * @return this
 */

Server.prototype.cacheDir = function (cache_dir) {
    this.options.cache_dir = cache_dir;
    return this;
};

/**
 * Whitelist image sizes. Image sizes are specified using 'WIDTHxHEIGHT-ORIENTATION'
 * where any parameter can be omitted, e.g. '200x300-centre' or just '200x' to
 * allow images with a width of 200 and any height. Pass `false` to disable
 * the whitelist.
 *
 * @param {String|Array|Boolean} whitelist
 * @return this
 */

Server.prototype.whitelist = function (whitelist) {
    if (!Array.isArray(whitelist)) {
        whitelist = [ whitelist ];
    }
    this.options.whitelist = whitelist;
    return this;
};

/**
 * Blacklist image sizes. The parameters are the same as `whitelist()`.
 *
 * @param {String|Array|Boolean} blacklist
 * @return this
 */

Server.prototype.blacklist = function (blacklist) {
    if (!Array.isArray(blacklist)) {
        blacklist = [ blacklist ];
    }
    this.options.blacklist = blacklist;
    return this;
};

/**
 * Set the rewriting strategy. Accepted tokens are :path (dirname), :file.:ext (basename),
 * :size (e.g. 200x200, 300x or 200x100-centre). Size is optional and is used to resize
 * the image on demand. Pass `false` to disable url rewriting.
 *
 * Example using the default '/:path/:size/:file.:ext':
 *
 *    /images/foobar.jpg => serves the unaltered image
 *    /images/200x300-centre/foobar.jpg => resizes /images/foobar.jpg to be exactly
 *                                         200x300, cropping from the centre
 *    /images/400x/foobar.jpg => resizes /images/foobar.jpg to be 400 pixels wide
 *
 * Another example '/:path/:file-:size.:ext'
 *
 *    /images/foobar-200x300-centre.jpg
 *    /images/foobar-400x.jpg
 *
 * @param {String|Boolean} url_rewrite
 * @return this
 */

Server.prototype.urlRewrite = function (url_rewrite) {
    this.options.url_rewrite = url_rewrite;
    return this;
};

/**
 * Set maxAge of static provider.
 *
 * Browser cache maxAge in milliseconds. defaults to 0
 *
 * @param {Number} maxAge
 * @return this
 */

Server.prototype.maxAge = function (maxAge) {
    this.options.maxAge = maxAge;
    return this;
};

/**
 * Debug the server.
 *
 * @param {Boolean} enable (optional)
 * @return this
 */

Server.prototype.debug = function (enable) {
    if (typeof enable === 'undefined') {
        enable = true;
    }
    this.options.debug = !!enable;
    return this;
};

/**
 * Output a debug msg.
 *
 * @param {String} msg
 * @param {Array} args
 * @api private
 */

Server.prototype.info = function (msg, args) {
    if (!this.options.debug) {
        return;
    }
    args = Array.prototype.slice.call(arguments);
    args[0] = 'imgr: ' + args[0];
    console.log.apply(console, args);
};

/**
 * Bind an express app and begin serving images.
 *
 * @param {ExpressApp} express
 * @return this
 */

Server.prototype.using = function (app) {
    var middleware = this.middleware();
    if (this.options.as_route) {
        var namespace = this.options.namespace.replace(/\/$/, '')
          , namespace_prefix = new RegExp('^' + namespace + '/');
        app.get(namespace_prefix, middleware);
        app.head(namespace_prefix, middleware);
    } else {
        app.use(middleware);
    }
};

/**
 *
 */

Server.prototype.resizeImage = function (src, dest, parameters, callback) {
    var self = this;
    fs.stat(src, function handleOriginalStat(err, size) {
        if (err | !size) {
            self.options.trace('missing_original');
            return callback();
        }

        self.info('compiling %s', dest);

        //Resize / crop as necessary
        var imgr = self.imgr.load(src, self.options);
        if (parameters.width) {
            if (parameters.height) {
                self.options.trace('resize_adaptive');
                imgr.adaptiveResize(parameters.width, parameters.height, parameters.orientation);
            } else {
                self.options.trace('resize_width');
                imgr.resizeToWidth(parameters.width);
            }
        } else if (parameters.height) {
            self.options.trace('resize_height');
            imgr.resizeToHeight(parameters.height);
        }

        //Save the image
        imgr.save(dest, function handleImgrSave(err) {
            if (err) {
                self.options.trace('gm_fail');
                self.info('conversion error: %s', err);
                return callback(err);
            }
            callback();
        });
    });
};

/**
 * Get imgr middleware
 *
 * @return {Function} middleware
 */

Server.prototype.middleware = function () {
    var namespace = this.options.namespace.replace(/\/$/, '')
      , namespace_prefix = new RegExp('^' + namespace + '/')
      , base_dir = this.path.replace(/\/$/, '')
      , cache_dir = this.options.cache_dir.replace(/\/$/, '')
      , whitelist = this.options.whitelist
      , blacklist = this.options.blacklist
      , maxAge = this.options.maxAge
      , trace = this.options.trace
      , info = this.info.bind(this)
      , self = this;

    //Setup the static servers
    var base_static = express.static(base_dir, { maxAge: maxAge })
      , cache_static = base_static;
    if (cache_dir !== base_dir) {
        cache_static = express.static(cache_dir, { maxAge: maxAge });
    }

    if (whitelist) {
        info('whitelist: [%s]', whitelist.join(', '));
        whitelist = utils.createSet(whitelist);
    }
    if (blacklist) {
        info('blacklist: [%s]', blacklist.join(', '));
        blacklist = utils.createSet(blacklist);
    }

    function tryContentDirectory(request, response, next) {
        info('trying to serve %s', path.join(base_dir, request.url));
        base_static(request, response, next);
    }

    function tryCacheDirectory(request, response, next) {
        info('trying to serve %s', path.join(cache_dir, request.url));
        cache_static(request, response, next);
    }

    function resizeImage(request, response, next) {
        info('extracting image parameters from url');
        var parameters = self.parse(request);
        if (!parameters) {
            return next();
        }

        info('extracted size %s', parameters.size);

        var src_image = path.join(base_dir, decodeURIComponent(parameters.path))
          , dest_image = path.join(cache_dir, decodeURIComponent(request.url));

        //Check for blacklisted and whitelisted parameters
        var size_check = parameters.size.replace(/-.+$/, '');
        if (blacklist && size_check in blacklist) {
            info('image size is in blacklist');
            trace('blacklist_hit');
            response.status(403);
            return response.send();
        } else if (whitelist) {
            var sizes = size_check.split('x')
              , allowed = false
              , to_check = [
                    size_check
                  , sizes[0] + 'x*'
                  , '*x' + sizes[1]
                ];
            for (var i = 0; i < 3; i++) {
                if (to_check[i] in whitelist) {
                    allowed = true;
                    break;
                }
            }
            if (!allowed) {
                info('image size is not in whitelist');
                trace('whitelist_miss');
                response.status(403);
                return response.send();
            }
        }

        info('image size is allowed');

        self.resizeImage(src_image, dest_image, parameters, next);
    }

    var middleware = [
        resizeImage
      , tryCacheDirectory
    ];

    if (this.options.try_cache) {
        middleware.unshift(tryCacheDirectory);
    }
    if (this.options.try_content) {
        middleware.unshift(tryContentDirectory);
    }

    return function imgrMiddleware(request, response, next) {
        if (namespace && !namespace_prefix.test(request.url)) {
            return next();
        }

        //Redirect if there's a querystring?
        if (self.options.querystring_301 && request.url.indexOf('?') >= 0) {
            trace('trim_querystring');
            return response.redirect(301, request.url.replace(/\?.*$/, ''));
        }

        //Remove the namespace
        var original_url = request.url;
        request.url = request.url.substr(namespace.length);
        info('request url is %s', request.url);

        async.eachSeries(middleware, function (stage, next_stage) {
            stage(request, response, next_stage);
        }, function () {
            request.url = original_url;
            next.apply(null, arguments);
        });
    };
};

/**
 * Parse the parameters of the request.
 *
 * @param {Request} request
 * @return {Object}
 */

Server.prototype.parse = function (request) {
    if (!this.parseRegexp) {
        this.parseRegexp = this.compileRegexp(this.options.url_rewrite);
    }
    return this.parseRegexp(request.url);
};

/**
 * Compile a URL regexp.
 *
 * @param {String} pattern
 * @return {Object|Boolean} regexp
 * @api private
 */

Server.prototype.compileRegexp = function (pattern) {
    if (!pattern) {
        return function compileRegexp() {
            return false;
        };
    }
    var directive_match = /:([a-z]+)/g
      , directives = []
      , match;
    pattern = pattern.replace(/\(/g, '(?:').replace(/\./g, '\\.');
    while ((match = directive_match.exec(pattern))) {
        directives.push(match[1]);
    }
    pattern = pattern.replace(':size', '(\\d+x|x+\\d+|\\d+x\\d+(?:-[a-z]+)?)')
                     .replace('/:path', '(?:/(.+?))?')
                     .replace(':ext', '([^/]+?)')
                     .replace(':file', '([^/]+?)');
    var regexp = new RegExp('^' + pattern + '$');
    return function compileRegexp(url) {
        var match = url.match(regexp);
        if (!match) {
            return false;
        }
        var parsed = {}, result = {};
        for (var i = 0, l = directives.length; i < l; i++) {
            parsed[directives[i]] = match[i + 1];
        }
        var filename = parsed.file + (parsed.ext ? '.' + parsed.ext : '');
        result.path = path.join(parsed.path || '', filename);
        if (parsed.size) {
            result.size = parsed.size;
            result.width = parsed.size.split('x', 2);
            result.height = (result.width[1] || '').split('-', 2);
            result.orientation = result.height[1] || null;
            result.height = Number(result.height[0]) || null;
            result.width = Number(result.width[0]) || null;
            if (result.orientation) {
                switch (result.orientation) {
                case 'top':
                    result.orientation = imgr.TOP;
                    break;
                case 'left':
                    result.orientation = imgr.LEFT;
                    break;
                case 'right':
                    result.orientation = imgr.RIGHT;
                    break;
                case 'bottom':
                    result.orientation = imgr.BOTTOM;
                    break;
                default:
                    result.orientation = imgr.CENTRE;
                    break;
                }
            }
        }
        return result;
    };
};
<commit_msg>Added optional middleware hook<commit_after>/**
 * Module dependencies.
 */

var express = require('express')
  , fs = require('fs')
  , path = require('path')
  , utils = require('./utils')
  , async = require('async')
  , imgr = require('./constants');

/**
 * Default options.
 */

var default_options = {
    namespace: '/'
  , cache_dir: '/tmp/imgr'
  , url_rewrite: '/:path/:size/:file.:ext'
  , whitelist: false
  , blacklist: false
  , debug: false
  , as_route: false
  , querystring_301: true
  , try_content: true
  , try_cache: true
  , trace: function () {}
};

/**
 * Create a new static image server.
 *
 * @param {String} path - where to serve images from
 * @param {Object} options (optional)
 */

var Server = exports.Server = function (path, options, imgr) {
    this.path = path;
    this.imgr = imgr;
    this.options = utils.mergeDefaults(options, default_options);
};

/**
 * Set the image namespace.
 *
 * @param {String} namespace - e.g. /images
 * @return this
 */

Server.prototype.namespace = function (namespace) {
    this.options.namespace = namespace || '/';
    return this;
};

/**
 * Set the cached/compiled image directory.
 *
 * @param {String} path
 * @return this
 */

Server.prototype.cacheDir = function (cache_dir) {
    this.options.cache_dir = cache_dir;
    return this;
};

/**
 * Whitelist image sizes. Image sizes are specified using 'WIDTHxHEIGHT-ORIENTATION'
 * where any parameter can be omitted, e.g. '200x300-centre' or just '200x' to
 * allow images with a width of 200 and any height. Pass `false` to disable
 * the whitelist.
 *
 * @param {String|Array|Boolean} whitelist
 * @return this
 */

Server.prototype.whitelist = function (whitelist) {
    if (!Array.isArray(whitelist)) {
        whitelist = [ whitelist ];
    }
    this.options.whitelist = whitelist;
    return this;
};

/**
 * Blacklist image sizes. The parameters are the same as `whitelist()`.
 *
 * @param {String|Array|Boolean} blacklist
 * @return this
 */

Server.prototype.blacklist = function (blacklist) {
    if (!Array.isArray(blacklist)) {
        blacklist = [ blacklist ];
    }
    this.options.blacklist = blacklist;
    return this;
};

/**
 * Set the rewriting strategy. Accepted tokens are :path (dirname), :file.:ext (basename),
 * :size (e.g. 200x200, 300x or 200x100-centre). Size is optional and is used to resize
 * the image on demand. Pass `false` to disable url rewriting.
 *
 * Example using the default '/:path/:size/:file.:ext':
 *
 *    /images/foobar.jpg => serves the unaltered image
 *    /images/200x300-centre/foobar.jpg => resizes /images/foobar.jpg to be exactly
 *                                         200x300, cropping from the centre
 *    /images/400x/foobar.jpg => resizes /images/foobar.jpg to be 400 pixels wide
 *
 * Another example '/:path/:file-:size.:ext'
 *
 *    /images/foobar-200x300-centre.jpg
 *    /images/foobar-400x.jpg
 *
 * @param {String|Boolean} url_rewrite
 * @return this
 */

Server.prototype.urlRewrite = function (url_rewrite) {
    this.options.url_rewrite = url_rewrite;
    return this;
};

/**
 * Set maxAge of static provider.
 *
 * Browser cache maxAge in milliseconds. defaults to 0
 *
 * @param {Number} maxAge
 * @return this
 */

Server.prototype.maxAge = function (maxAge) {
    this.options.maxAge = maxAge;
    return this;
};

/**
 * Debug the server.
 *
 * @param {Boolean} enable (optional)
 * @return this
 */

Server.prototype.debug = function (enable) {
    if (typeof enable === 'undefined') {
        enable = true;
    }
    this.options.debug = !!enable;
    return this;
};

/**
 * Output a debug msg.
 *
 * @param {String} msg
 * @param {Array} args
 * @api private
 */

Server.prototype.info = function (msg, args) {
    if (!this.options.debug) {
        return;
    }
    args = Array.prototype.slice.call(arguments);
    args[0] = 'imgr: ' + args[0];
    console.log.apply(console, args);
};

/**
 * Bind an express app and begin serving images.
 *
 * @param {ExpressApp} express
 * @return this
 */

Server.prototype.using = function (app) {
    var middleware = this.middleware();
    if (this.options.as_route) {
        var namespace = this.options.namespace.replace(/\/$/, '')
          , namespace_prefix = new RegExp('^' + namespace + '/');
        app.get(namespace_prefix, middleware);
        app.head(namespace_prefix, middleware);
    } else {
        app.use(middleware);
    }
};

/**
 *
 */

Server.prototype.resizeImage = function (src, dest, parameters, callback) {
    var self = this;
    fs.stat(src, function handleOriginalStat(err, size) {
        if (err | !size) {
            self.options.trace('missing_original');
            return callback();
        }

        self.info('compiling %s', dest);

        //Resize / crop as necessary
        var imgr = self.imgr.load(src, self.options);
        if (parameters.width) {
            if (parameters.height) {
                self.options.trace('resize_adaptive');
                imgr.adaptiveResize(parameters.width, parameters.height, parameters.orientation);
            } else {
                self.options.trace('resize_width');
                imgr.resizeToWidth(parameters.width);
            }
        } else if (parameters.height) {
            self.options.trace('resize_height');
            imgr.resizeToHeight(parameters.height);
        }

        //Save the image
        imgr.save(dest, function handleImgrSave(err) {
            if (err) {
                self.options.trace('gm_fail');
                self.info('conversion error: %s', err);
                return callback(err);
            }
            callback();
        });
    });
};

/**
 * Get imgr middleware
 *
 * @return {Function} middleware
 */

Server.prototype.middleware = function () {
    var namespace = this.options.namespace.replace(/\/$/, '')
      , namespace_prefix = new RegExp('^' + namespace + '/')
      , base_dir = this.path.replace(/\/$/, '')
      , cache_dir = this.options.cache_dir.replace(/\/$/, '')
      , whitelist = this.options.whitelist
      , blacklist = this.options.blacklist
      , maxAge = this.options.maxAge
      , trace = this.options.trace
      , info = this.info.bind(this)
      , self = this;

    //Setup the static servers
    var base_static = express.static(base_dir, { maxAge: maxAge })
      , cache_static = base_static;
    if (cache_dir !== base_dir) {
        cache_static = express.static(cache_dir, { maxAge: maxAge });
    }

    if (whitelist) {
        info('whitelist: [%s]', whitelist.join(', '));
        whitelist = utils.createSet(whitelist);
    }
    if (blacklist) {
        info('blacklist: [%s]', blacklist.join(', '));
        blacklist = utils.createSet(blacklist);
    }

    function tryContentDirectory(request, response, next) {
        info('trying to serve %s', path.join(base_dir, request.url));
        base_static(request, response, next);
    }

    function tryCacheDirectory(request, response, next) {
        info('trying to serve %s', path.join(cache_dir, request.url));
        cache_static(request, response, next);
    }

    function resizeImage(request, response, next) {
        info('extracting image parameters from url');
        var parameters = self.parse(request);
        if (!parameters) {
            return next();
        }

        info('extracted size %s', parameters.size);

        var src_image = path.join(base_dir, decodeURIComponent(parameters.path))
          , dest_image = path.join(cache_dir, decodeURIComponent(request.url));

        //Check for blacklisted and whitelisted parameters
        var size_check = parameters.size.replace(/-.+$/, '');
        if (blacklist && size_check in blacklist) {
            info('image size is in blacklist');
            trace('blacklist_hit');
            response.status(403);
            return response.send();
        } else if (whitelist) {
            var sizes = size_check.split('x')
              , allowed = false
              , to_check = [
                    size_check
                  , sizes[0] + 'x*'
                  , '*x' + sizes[1]
                ];
            for (var i = 0; i < 3; i++) {
                if (to_check[i] in whitelist) {
                    allowed = true;
                    break;
                }
            }
            if (!allowed) {
                info('image size is not in whitelist');
                trace('whitelist_miss');
                response.status(403);
                return response.send();
            }
        }

        info('image size is allowed');

        self.resizeImage(src_image, dest_image, parameters, next);
    }

    var middleware = [
        resizeImage
      , tryCacheDirectory
    ];

    if(this.options.hook) {
        middleware.unshift(this.options.hook);
    }
    if (this.options.try_cache) {
        middleware.unshift(tryCacheDirectory);
    }
    if (this.options.try_content) {
        middleware.unshift(tryContentDirectory);
    }

    return function imgrMiddleware(request, response, next) {
        if (namespace && !namespace_prefix.test(request.url)) {
            return next();
        }

        //Redirect if there's a querystring?
        if (self.options.querystring_301 && request.url.indexOf('?') >= 0) {
            trace('trim_querystring');
            return response.redirect(301, request.url.replace(/\?.*$/, ''));
        }

        //Remove the namespace
        var original_url = request.url;
        request.url = request.url.substr(namespace.length);
        info('request url is %s', request.url);

        async.eachSeries(middleware, function (stage, next_stage) {
            stage(request, response, next_stage);
        }, function () {
            request.url = original_url;
            next.apply(null, arguments);
        });
    };
};

/**
 * Parse the parameters of the request.
 *
 * @param {Request} request
 * @return {Object}
 */

Server.prototype.parse = function (request) {
    if (!this.parseRegexp) {
        this.parseRegexp = this.compileRegexp(this.options.url_rewrite);
    }
    return this.parseRegexp(request.url);
};

/**
 * Compile a URL regexp.
 *
 * @param {String} pattern
 * @return {Object|Boolean} regexp
 * @api private
 */

Server.prototype.compileRegexp = function (pattern) {
    if (!pattern) {
        return function compileRegexp() {
            return false;
        };
    }
    var directive_match = /:([a-z]+)/g
      , directives = []
      , match;
    pattern = pattern.replace(/\(/g, '(?:').replace(/\./g, '\\.');
    while ((match = directive_match.exec(pattern))) {
        directives.push(match[1]);
    }
    pattern = pattern.replace(':size', '(\\d+x|x+\\d+|\\d+x\\d+(?:-[a-z]+)?)')
                     .replace('/:path', '(?:/(.+?))?')
                     .replace(':ext', '([^/]+?)')
                     .replace(':file', '([^/]+?)');
    var regexp = new RegExp('^' + pattern + '$');
    return function compileRegexp(url) {
        var match = url.match(regexp);
        if (!match) {
            return false;
        }
        var parsed = {}, result = {};
        for (var i = 0, l = directives.length; i < l; i++) {
            parsed[directives[i]] = match[i + 1];
        }
        var filename = parsed.file + (parsed.ext ? '.' + parsed.ext : '');
        result.path = path.join(parsed.path || '', filename);
        if (parsed.size) {
            result.size = parsed.size;
            result.width = parsed.size.split('x', 2);
            result.height = (result.width[1] || '').split('-', 2);
            result.orientation = result.height[1] || null;
            result.height = Number(result.height[0]) || null;
            result.width = Number(result.width[0]) || null;
            if (result.orientation) {
                switch (result.orientation) {
                case 'top':
                    result.orientation = imgr.TOP;
                    break;
                case 'left':
                    result.orientation = imgr.LEFT;
                    break;
                case 'right':
                    result.orientation = imgr.RIGHT;
                    break;
                case 'bottom':
                    result.orientation = imgr.BOTTOM;
                    break;
                default:
                    result.orientation = imgr.CENTRE;
                    break;
                }
            }
        }
        return result;
    };
};
<|endoftext|>


<commit_before>/**
 * @fileOverview The Read of the CRUD ops.
 */
var Promise = require('bluebird');

var Controller = require('../controller-base');
var PaginationMidd = require('../pagination.midd');

/**
 * The Read of the CRUD ops.
 *
 * @param {app.ctrl.ControllerCrudBase} base DI The callee instance.
 * @constructor
 */
var Read = module.exports = Controller.extend(function(base) {
  /** @type {app.ctrl.ControllerCrudBase} Options */
  this.base = base;

  // prep pagination
  this.pagination = new PaginationMidd(this.base, this);
});

/**
 * Will force setup the readlist command middleware which depends on
 * consumer configuration.
 *
 */
Read.prototype.setupReadList = function() {
  this.base.readList = [this.base._prepResponse.bind(this.base)];
  if (this.base.opts.pagination) {
    this.base.readList.push(this.pagination.paginate());
    this.base.readList.push(this._readListResults.bind(this));
  } else {
    this.base.readList.push(this.readList.bind(this));
  }
};

/**
 * Read all the items.
 *
 * @param {Object} req The request Object.
 * @param {Object} res The response Object.
 */
Read.prototype.readList = function(req, res) {
  var self = this;

  var query = this.parseParams(req);

  if (this.base.opts.ownUser) {
    query[this.base.opts.ownUserSchemaProperty] =
      req[this.base.opts.ownUserRequestProperty];
  }

  this.base.entity[this.base.opts.entityRead](query)
    .then(function(results) {
      if (self.base.isJson(req)) {
        var sanitizedDocs = self.base.hidePrivatesArray(results);
        res.json(200, sanitizedDocs);
        return;
      }
      res.locals.items = results;
      res.locals.count = results.length;
      self._readListResults(req, res);
    }).catch(this._handleReadError.bind(this, req, res, this.base.opts.listView));
};

/**
 * Handle item pagination results.
 *
 * @param {Object} req The request Object.
 * @param {Object} res The response Object.
 * @private
 */
Read.prototype._readListResults = function(req, res) {
  if (this.base.isJson(req)) {
    // paginator handled it
    return;
  }

  if (this.base.opts.listView) {
    return res.render(this.base.opts.listView);
  }

  // render the template and store in response locals.
  res.locals[Read.VIEW_OUTPUT_KEY] = this.base.compiled.list(res.locals);
  if (this.base.opts.layoutView) {
    res.render(this.base.opts.layoutView);
  } else {
    res.send(res.locals[Read.VIEW_OUTPUT_KEY]);
  }
};

/**
 * Handle a single item view.
 *
 * @param {Object} req The request Object.
 * @param {Object} res The response Object.
 * @return {Promise} A promise.
 */
Read.prototype.readOne = Promise.method(function(req, res) {
  // attempt to fetch the record...
  var query = new Object(null);
  query[this.base.opts.urlField] = req.params.id;
  if (this.base.opts.ownUser) {
    query[this.base.opts.ownUserSchemaProperty] =
      req[this.base.opts.ownUserRequestProperty];
  }

  return this.base.entity[this.base.opts.entityReadOne](query)
    .then(this.base._handleSuccess.bind(this.base, req, res))
    .catch(this._handleReadError.bind(this, req, res, this.base.views.view));
});

/**
 * Handle Error from Read OP.
 *
 * @param {Object} req The request Object.
 * @param {Object} res The response Object.
 * @param {string} view View to render.
 * @param {Error|string} err The error.
 * @private
 */
Read.prototype._handleReadError = function(req, res, view, err) {
  if (this.base.isJson(req)) {
    res.status(401).json(this.base.jsonError(err));
  } else {
    this.base.addError(res, err);
    res.render(view);
  }
};

/**
 * Parses the request parameters (query string) and translates them into
 * query for the db.
 *
 * @param {express.Request} req The request object.
 * @return {Object} The query to use.
 */
Read.prototype.parseParams = function(req) {
  var query = {};
  if (!req.query) {
    return query;
  }

  var keys = Object.keys(req.query);
  if (!keys.length) {
    return query;
  }

  if (req.query.from) {
    if (req.query.to) {
      query[this.base.opts.dateField] = {between: [
        req.query.from,
        req.query.to,
      ]};
    } else {
      query[this.base.opts.dateField] = {gte: req.query.from};
    }
  } else if (req.query.to) {
    query[this.base.opts.dateField] = {lte: req.query.to};
  }

  var skipKeys = ['from', 'to'];

  keys.forEach(function(key) {
    if (skipKeys.indexOf(key) > -1) {
      return;
    }
    query[key] = req.query[key];
  });

  return query;
};
<commit_msg>exclude 'page' and 'limit' query variables<commit_after>/**
 * @fileOverview The Read of the CRUD ops.
 */
var Promise = require('bluebird');

var Controller = require('../controller-base');
var PaginationMidd = require('../pagination.midd');

/**
 * The Read of the CRUD ops.
 *
 * @param {app.ctrl.ControllerCrudBase} base DI The callee instance.
 * @constructor
 */
var Read = module.exports = Controller.extend(function(base) {
  /** @type {app.ctrl.ControllerCrudBase} Options */
  this.base = base;

  // prep pagination
  this.pagination = new PaginationMidd(this.base, this);
});

/**
 * Will force setup the readlist command middleware which depends on
 * consumer configuration.
 *
 */
Read.prototype.setupReadList = function() {
  this.base.readList = [this.base._prepResponse.bind(this.base)];
  if (this.base.opts.pagination) {
    this.base.readList.push(this.pagination.paginate());
    this.base.readList.push(this._readListResults.bind(this));
  } else {
    this.base.readList.push(this.readList.bind(this));
  }
};

/**
 * Read all the items.
 *
 * @param {Object} req The request Object.
 * @param {Object} res The response Object.
 */
Read.prototype.readList = function(req, res) {
  var self = this;

  var query = this.parseParams(req);

  if (this.base.opts.ownUser) {
    query[this.base.opts.ownUserSchemaProperty] =
      req[this.base.opts.ownUserRequestProperty];
  }

  this.base.entity[this.base.opts.entityRead](query)
    .then(function(results) {
      if (self.base.isJson(req)) {
        var sanitizedDocs = self.base.hidePrivatesArray(results);
        res.json(200, sanitizedDocs);
        return;
      }
      res.locals.items = results;
      res.locals.count = results.length;
      self._readListResults(req, res);
    }).catch(this._handleReadError.bind(this, req, res, this.base.opts.listView));
};

/**
 * Handle item pagination results.
 *
 * @param {Object} req The request Object.
 * @param {Object} res The response Object.
 * @private
 */
Read.prototype._readListResults = function(req, res) {
  if (this.base.isJson(req)) {
    // paginator handled it
    return;
  }

  if (this.base.opts.listView) {
    return res.render(this.base.opts.listView);
  }

  // render the template and store in response locals.
  res.locals[Read.VIEW_OUTPUT_KEY] = this.base.compiled.list(res.locals);
  if (this.base.opts.layoutView) {
    res.render(this.base.opts.layoutView);
  } else {
    res.send(res.locals[Read.VIEW_OUTPUT_KEY]);
  }
};

/**
 * Handle a single item view.
 *
 * @param {Object} req The request Object.
 * @param {Object} res The response Object.
 * @return {Promise} A promise.
 */
Read.prototype.readOne = Promise.method(function(req, res) {
  // attempt to fetch the record...
  var query = new Object(null);
  query[this.base.opts.urlField] = req.params.id;
  if (this.base.opts.ownUser) {
    query[this.base.opts.ownUserSchemaProperty] =
      req[this.base.opts.ownUserRequestProperty];
  }

  return this.base.entity[this.base.opts.entityReadOne](query)
    .then(this.base._handleSuccess.bind(this.base, req, res))
    .catch(this._handleReadError.bind(this, req, res, this.base.views.view));
});

/**
 * Handle Error from Read OP.
 *
 * @param {Object} req The request Object.
 * @param {Object} res The response Object.
 * @param {string} view View to render.
 * @param {Error|string} err The error.
 * @private
 */
Read.prototype._handleReadError = function(req, res, view, err) {
  if (this.base.isJson(req)) {
    res.status(401).json(this.base.jsonError(err));
  } else {
    this.base.addError(res, err);
    res.render(view);
  }
};

/**
 * Parses the request parameters (query string) and translates them into
 * query for the db.
 *
 * @param {express.Request} req The request object.
 * @return {Object} The query to use.
 */
Read.prototype.parseParams = function(req) {
  var query = {};
  if (!req.query) {
    return query;
  }

  var keys = Object.keys(req.query);
  if (!keys.length) {
    return query;
  }

  if (req.query.from) {
    if (req.query.to) {
      query[this.base.opts.dateField] = {between: [
        req.query.from,
        req.query.to,
      ]};
    } else {
      query[this.base.opts.dateField] = {gte: req.query.from};
    }
  } else if (req.query.to) {
    query[this.base.opts.dateField] = {lte: req.query.to};
  }

  var skipKeys = ['from', 'to', 'limit', 'page'];

  keys.forEach(function(key) {
    if (skipKeys.indexOf(key) > -1) {
      return;
    }
    query[key] = req.query[key];
  });

  return query;
};
<|endoftext|>


<commit_before>const express = require('express');
const app = express();
const mongoose = require('mongoose');

const planRouter = require(__dirname + 'routes/planRouter.js');

mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost/plan_db');

app.use('/api', planRouter);

var PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log('Server is live at: ' + PORT));
<commit_msg>fixed server<commit_after>const express = require('express');
const app = express();
const mongoose = require('mongoose');

const planRouter = require(__dirname + '/../routes/planRoutes.js');

mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost/plan_db');

app.use('/api', planRouter);

var PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log('Server is live at: ' + PORT));
<|endoftext|>


<commit_before> (function() {
    'use strict';

    var DIR                 = './',
        DIR_ROOT            = __dirname + '/..',
        DIR_SERVER          = DIR + 'server/',

        path                = require('path'),

        join                = require('join-io'),
        minify              = require('minify'),

        joinFunc            = join({
            dir     : DIR_ROOT,
            minify  : true
        }),

        minifyFunc          = minify({
            dir     : DIR_ROOT
        }),

        socket              = require(DIR_SERVER + 'socket'),
        Console             = require(DIR_SERVER + 'console');

    module.exports          = start;

    module.exports.middle           = function(prefix, notMinify) {
        return getServe(prefix, notMinify);
    };

    /*
     * @params options
     *
     * server, onMsg, prefix, notMinify
     */
    function start(options) {
        var sock,
            middle,
            o           = options || {};

        if (o.socket)
            sock    = o.socket;
        else if (o.server)
            sock    = socket.listen(o.server);

        middle  = getServe(o.prefix, o.isMinify),

        Console(sock, o.onMsg);

        return middle;
    }

    function checkMinify(isMinify) {
        var is;

        if (typeof isMinify === 'function')
            is  = isMinify();
        else if (isMinify === undefined)
            is  = true;
        else
            is  = isMinify;

        return is;
    }

    function getServe(prefix, isMinify) {
        var is = checkMinify(isMinify);

        if (!prefix)
            prefix = '/console';

        return serve.bind(null, prefix, is);
    }

    function serve(prefix, isMinify, req, res, next) {
        var url         = req.url,
            regExp      = new RegExp('^' + prefix),
            regExpJoin  = new RegExp('^' + prefix + '/join'),

            isConsole   = url.match(regExp),
            isJoin      = url.match(regExpJoin),

            URL     = prefix + '/console.js',
            PATH    = '/lib/client/console.js';

        if (!isConsole) {
            next();
        } else {
            if (url === URL)
                url = PATH;
            else
                url = url.replace(prefix, '');

            req.url = url;

            if (isJoin)
                joinFunc(req, res, next);
            else if (isMinify)
                minifyFunc(req, res, next);
            else
                res.sendFile(path.normalize(DIR_ROOT + url));
        }
    }

})();
<commit_msg>fix(console) isMinify on a fly<commit_after> (function() {
    'use strict';

    var DIR                 = './',
        DIR_ROOT            = __dirname + '/..',
        DIR_SERVER          = DIR + 'server/',

        path                = require('path'),

        join                = require('join-io'),
        minify              = require('minify'),

        minifyFunc          = minify({
            dir     : DIR_ROOT
        }),

        socket              = require(DIR_SERVER + 'socket'),
        Console             = require(DIR_SERVER + 'console');

    module.exports          = start;

    module.exports.middle           = function(prefix, notMinify) {
        return getServe(prefix, notMinify);
    };

    /*
     * @params options
     *
     * server, onMsg, prefix, notMinify
     */
    function start(options) {
        var sock,
            middle,
            o           = options || {};

        if (o.socket)
            sock    = o.socket;
        else if (o.server)
            sock    = socket.listen(o.server);

        middle  = getServe(o.prefix, o.isMinify),

        Console(sock, o.onMsg);

        return middle;
    }

    function checkMinify(isMinify) {
        var is;

        if (typeof isMinify === 'function')
            is  = isMinify();
        else if (isMinify === undefined)
            is  = true;
        else
            is  = isMinify;

        return is;
    }

    function getServe(prefix, isMinify) {
        if (!prefix)
            prefix = '/console';

        return serve.bind(null, prefix, isMinify);
    }

    function serve(prefix, isMinify, req, res, next) {
        var joinFunc,
            isMin       = checkMinify(isMinify),
            url         = req.url,
            regExp      = new RegExp('^' + prefix),
            regExpJoin  = new RegExp('^' + prefix + '/join'),

            isConsole   = url.match(regExp),
            isJoin      = url.match(regExpJoin),

            URL         = prefix + '/console.js',
            PATH        = '/lib/client/console.js';

        if (!isConsole) {
            next();
        } else {
            if (url === URL)
                url = PATH;
            else
                url = url.replace(prefix, '');

            req.url = url;

            if (isJoin) {
                joinFunc = join({
                    dir     : DIR_ROOT,
                    minify  : isMin
                });

                joinFunc(req, res, next);
            } else if (isMin) {
                minifyFunc(req, res, next);
            } else {
                url = path.normalize(DIR_ROOT + url);
                res.sendFile(url);
            }
        }
    }

})();
<|endoftext|>


<commit_before>// Include Restify REST framework
// http://mcavage.me/node-restify/
var restify = require('restify');

// Create the server object
var server = restify.createServer({
	name: 'WinDnsApi'
});

// Special logic for CURL's keep-alive behavior
server.pre(restify.pre.userAgentConnection());

// DNS Updates
function doDnsSet(req, res, next) {

	var RRType = req.params.type.toUpperCase(),
		ZoneName = req.params.zone,
		NodeName = req.params.node,
		IpAddress = req.params.ip;

	// Validate RRType
	if( RRType != "A" ) {
		return next(new restify.InvalidArgumentError("You specified an invalid record type ('" + RRType + "'). Currently, only the 'A' (alias) record type is supported.  e.g. /dns/my.zone/A/.."));
	}

	// Validate Zone Name
	if( ZoneName.match(/[^A-Za-z0-9\.-]+/g) !== null ) {
		return next(new restify.InvalidArgumentError("Invalid zone name ('" + ZoneName + "'). Zone names can only contain letters, numbers, dashes (-), and dots (.)."));
	}

	// Validate Node Name
	if( NodeName.match(/[^A-Za-z0-9\.-]+/g) !== null ) {
		return next(new restify.InvalidArgumentError("Invalid node name ('" + NodeName + "'). Node names can only contain letters, numbers, dashes (-), and dots (.)."));
	}

	// Validate IP Address
	if( IpAddress.match(/^(([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)\.){3}([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)$/g) == null ) {
		return next(new restify.InvalidArgumentError("Invalid IP address ('" + IpAddress + "'). Currently, only IPv4 addresses are accepted."));
	}

	res.send({
		code: "RecordUpdated",
		zone: ZoneName,
		node: NodeName,
		type: RRType,
		ip: IpAddress,
		message: "The alias ('A') record '" + NodeName + "." + ZoneName + "' was successfully updated to '" + IpAddress + "'."
	});

}
server.get('/dns/:zone/:type/:node/set/:ip', doDnsSet);
server.head('/dns/:zone/:type/:node/set/:ip', doDnsSet);

// DNS Removal
function doDnsRemove(req, res, next) {

	var RRType = req.params.type.toUpperCase(),
		ZoneName = req.params.zone,
		NodeName = req.params.node;

	// Validate RRType
	if( RRType != "A" ) {
		return next(new restify.InvalidArgumentError("You specified an invalid record type ('" + RRType + "'). Currently, only the 'A' (alias) record type is supported.  e.g. /dns/my.zone/A/.."));
	}

	// Validate Zone Name
	if( ZoneName.match(/[^A-Za-z0-9\.-]+/g) !== null ) {
		return next(new restify.InvalidArgumentError("Invalid zone name ('" + ZoneName + "'). Zone names can only contain letters, numbers, dashes (-), and dots (.)."));
	}

	// Validate Node Name
	if( NodeName.match(/[^A-Za-z0-9\.-]+/g) !== null ) {
		return next(new restify.InvalidArgumentError("Invalid node name ('" + NodeName + "'). Node names can only contain letters, numbers, dashes (-), and dots (.)."));
	}

	res.send({
		code: "RecordRemoved",
		zone: ZoneName,
		node: NodeName,
		type: RRType,
		message: "The alias ('A') record '" + NodeName + "." + ZoneName + "' was successfully removed."
	});

}
server.get('/dns/:zone/:type/:node/remove', doDnsRemove);
server.head('/dns/:zone/:type/:node/remove', doDnsRemove);

// Start the server
server.listen(3111, function() {
	console.log('%s web server listening at %s', server.name, server.url);
});<commit_msg>Added DNS Commands<commit_after>// Include Restify REST framework
// http://mcavage.me/node-restify/
var restify = require('restify');

// Child process module
var exec = require('child_process').exec;

// Create the server object
var server = restify.createServer({
	name: 'WinDnsApi'
});

// Special logic for CURL's keep-alive behavior
server.pre(restify.pre.userAgentConnection());

// DNS Updates
function doDnsSet(req, res, next) {

	var RRType = req.params.type.toUpperCase(),
		ZoneName = req.params.zone,
		NodeName = req.params.node,
		IpAddress = req.params.ip;

	// Validate RRType
	if( RRType != "A" ) {
		return next(new restify.InvalidArgumentError("You specified an invalid record type ('" + RRType + "'). Currently, only the 'A' (alias) record type is supported.  e.g. /dns/my.zone/A/.."));
	}

	// Validate Zone Name
	if( ZoneName.match(/[^A-Za-z0-9\.-]+/g) !== null ) {
		return next(new restify.InvalidArgumentError("Invalid zone name ('" + ZoneName + "'). Zone names can only contain letters, numbers, dashes (-), and dots (.)."));
	}

	// Validate Node Name
	if( NodeName.match(/[^A-Za-z0-9\.-]+/g) !== null ) {
		return next(new restify.InvalidArgumentError("Invalid node name ('" + NodeName + "'). Node names can only contain letters, numbers, dashes (-), and dots (.)."));
	}

	// Validate IP Address
	if( IpAddress.match(/^(([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)\.){3}([1-9]?\d|1\d\d|25[0-5]|2[0-4]\d)$/g) == null ) {
		return next(new restify.InvalidArgumentError("Invalid IP address ('" + IpAddress + "'). Currently, only IPv4 addresses are accepted."));
	}

	var exec1 = exec('dnscmd /recordadd ' + ZoneName + ' ' + NodeName + ' ' + RRType + ' ' + IpAddress,
		function (error, stdout, stderr) {
			console.log('stdout: ' + stdout);
			console.log('stderr: ' + stderr);
			if (error !== null) {
				console.log('exec error: ' + error);
			}
		}
	);

	res.send({
		code: "RecordUpdated",
		zone: ZoneName,
		node: NodeName,
		type: RRType,
		ip: IpAddress,
		message: "The alias ('A') record '" + NodeName + "." + ZoneName + "' was successfully updated to '" + IpAddress + "'."
	});

}
server.get('/dns/:zone/:type/:node/set/:ip', doDnsSet);
server.head('/dns/:zone/:type/:node/set/:ip', doDnsSet);

// DNS Removal
function doDnsRemove(req, res, next) {

	var RRType = req.params.type.toUpperCase(),
		ZoneName = req.params.zone,
		NodeName = req.params.node;

	// Validate RRType
	if( RRType != "A" ) {
		return next(new restify.InvalidArgumentError("You specified an invalid record type ('" + RRType + "'). Currently, only the 'A' (alias) record type is supported.  e.g. /dns/my.zone/A/.."));
	}

	// Validate Zone Name
	if( ZoneName.match(/[^A-Za-z0-9\.-]+/g) !== null ) {
		return next(new restify.InvalidArgumentError("Invalid zone name ('" + ZoneName + "'). Zone names can only contain letters, numbers, dashes (-), and dots (.)."));
	}

	// Validate Node Name
	if( NodeName.match(/[^A-Za-z0-9\.-]+/g) !== null ) {
		return next(new restify.InvalidArgumentError("Invalid node name ('" + NodeName + "'). Node names can only contain letters, numbers, dashes (-), and dots (.)."));
	}

	var exec1 = exec('dnscmd /recorddelete ' + ZoneName + ' ' + NodeName + ' ' + RRType,
		function (error, stdout, stderr) {
			console.log('stdout: ' + stdout);
			console.log('stderr: ' + stderr);
			if (error !== null) {
				console.log('exec error: ' + error);
			}
		}
	);

	res.send({
		code: "RecordRemoved",
		zone: ZoneName,
		node: NodeName,
		type: RRType,
		message: "The alias ('A') record '" + NodeName + "." + ZoneName + "' was successfully removed."
	});

}
server.get('/dns/:zone/:type/:node/remove', doDnsRemove);
server.head('/dns/:zone/:type/:node/remove', doDnsRemove);

// Start the server
server.listen(3111, function() {
	console.log('%s web server listening at %s', server.name, server.url);
});<|endoftext|>


<commit_before>'use strict'

var write = require('../write')
var fastfall = require('fastfall')
var Packet = require('aedes-packet')
var through = require('through2')
var topicActions = fastfall([
  authorize,
  storeSubscriptions,
  subTopic
])

function SubscribeState (client, packet, finish, granted) {
  this.client = client
  this.packet = packet
  this.finish = finish
  this.granted = granted
}

function handleSubscribe (client, packet, done) {
  var broker = client.broker
  var subs = packet.subscriptions
  var granted = []

  broker._parallel(
    new SubscribeState(client, packet, done, granted),
    doSubscribe,
    subs,
    completeSubscribe)
}

function doSubscribe (sub, done) {
  // TODO this function should not be needed
  topicActions.call(this, sub, done)
}

function authorize (sub, done) {
  var client = this.client
  client.broker.authorizeSubscribe(client, sub, done)
}

function blockSys (func) {
  return function deliverSharp (packet, cb) {
    if (packet.topic.indexOf('$SYS') === 0) {
      cb()
    } else {
      func(packet, cb)
    }
  }
}

function Subscription (qos, func) {
  this.qos = qos
  this.func = func
}

function storeSubscriptions (sub, done) {
  var packet = this.packet
  var client = this.client
  var broker = client.broker
  var perst = broker.persistence

  if (sub && packet.subscriptions[0].topic !== sub.topic) {
    // don't call addSubscriptions per topic,
    // TODO change aedes subscribe handle, but this is a fast & dirty fix for now
    return done(null, sub)
  }

  if (packet.restore) {
    return done(null, sub)
  }

  perst.addSubscriptions(client, packet.subscriptions, function (err) {
    done(err, sub)
  })
}

function subTopic (sub, done) {
  if (!sub) {
    this.granted.push(128)
    return done()
  }

  var client = this.client
  var broker = client.broker
  var func = nop

  switch (sub.qos) {
    case 2:
    case 1:
      func = client.deliverQoS
      break
    default:
      func = client.deliver0
      break
  }

  if (sub.topic === '#') {
    func = blockSys(func)
  }

  this.granted.push(sub.qos)

  if (!client.subscriptions[sub.topic]) {
    client.subscriptions[sub.topic] = new Subscription(sub.qos, func)
    broker.subscribe(sub.topic, func, sendRetained)
  } else if (client.subscriptions[sub.topic].qos !== sub.qos) {
    broker.unsubscribe(sub.topic, client.subscriptions[sub.topic].func)
    client.subscriptions[sub.topic] = new Subscription(sub.qos, func)
    broker.subscribe(sub.topic, func, sendRetained)
  } else {
    sendRetained()
  }

  function sendRetained () {
    // first do a suback
    done()

    var persistence = broker.persistence

    var stream = persistence.createRetainedStream(sub.topic)

    stream.pipe(through.obj(function sendRetained (packet, enc, cb) {
      packet = new Packet(packet)
      // this should not be deduped
      packet.brokerId = null
      client.deliver0(packet, cb)
    }))
  }
}

function completeSubscribe (err) {
  var packet = this.packet
  var client = this.client
  var broker = client.broker
  var granted = this.granted
  var done = this.finish

  if (err) {
    return done(err)
  }

  if (packet.messageId) {
    broker.emit('subscribe', packet.subscriptions, client)
    write(client, new SubAck(packet, granted), done)
  } else {
    done()
  }
}

function SubAck (packet, granted) {
  this.cmd = 'suback'
  this.messageId = packet.messageId
  this.granted = granted
}

function nop () {}

module.exports = handleSubscribe
<commit_msg>emit subscribe event only on subscribes<commit_after>'use strict'

var write = require('../write')
var fastfall = require('fastfall')
var Packet = require('aedes-packet')
var through = require('through2')
var topicActions = fastfall([
  authorize,
  storeSubscriptions,
  subTopic
])

function SubscribeState (client, packet, finish, granted) {
  this.client = client
  this.packet = packet
  this.finish = finish
  this.granted = granted
}

function handleSubscribe (client, packet, done) {
  var broker = client.broker
  var subs = packet.subscriptions
  var granted = []

  broker._parallel(
    new SubscribeState(client, packet, done, granted),
    doSubscribe,
    subs,
    completeSubscribe)
}

function doSubscribe (sub, done) {
  // TODO this function should not be needed
  topicActions.call(this, sub, done)
}

function authorize (sub, done) {
  var client = this.client
  client.broker.authorizeSubscribe(client, sub, done)
}

function blockSys (func) {
  return function deliverSharp (packet, cb) {
    if (packet.topic.indexOf('$SYS') === 0) {
      cb()
    } else {
      func(packet, cb)
    }
  }
}

function Subscription (qos, func) {
  this.qos = qos
  this.func = func
}

function storeSubscriptions (sub, done) {
  var packet = this.packet
  var client = this.client
  var broker = client.broker
  var perst = broker.persistence

  if (sub && packet.subscriptions[0].topic !== sub.topic) {
    // don't call addSubscriptions per topic,
    // TODO change aedes subscribe handle, but this is a fast & dirty fix for now
    return done(null, sub)
  }

  if (packet.restore) {
    return done(null, sub)
  }

  perst.addSubscriptions(client, packet.subscriptions, function (err) {
    done(err, sub)
  })
}

function subTopic (sub, done) {
  if (!sub) {
    this.granted.push(128)
    return done()
  }

  var client = this.client
  var broker = client.broker
  var func = nop

  switch (sub.qos) {
    case 2:
    case 1:
      func = client.deliverQoS
      break
    default:
      func = client.deliver0
      break
  }

  if (sub.topic === '#') {
    func = blockSys(func)
  }

  this.granted.push(sub.qos)

  if (!client.subscriptions[sub.topic]) {
    client.subscriptions[sub.topic] = new Subscription(sub.qos, func)
    broker.subscribe(sub.topic, func, sendRetained)
  } else if (client.subscriptions[sub.topic].qos !== sub.qos) {
    broker.unsubscribe(sub.topic, client.subscriptions[sub.topic].func)
    client.subscriptions[sub.topic] = new Subscription(sub.qos, func)
    broker.subscribe(sub.topic, func, sendRetained)
  } else {
    sendRetained()
  }

  function sendRetained () {
    // first do a suback
    done()

    var persistence = broker.persistence

    var stream = persistence.createRetainedStream(sub.topic)

    stream.pipe(through.obj(function sendRetained (packet, enc, cb) {
      packet = new Packet(packet)
      // this should not be deduped
      packet.brokerId = null
      client.deliver0(packet, cb)
    }))
  }
}

function completeSubscribe (err) {
  var packet = this.packet
  var client = this.client
  var broker = client.broker
  var granted = this.granted
  var done = this.finish

  if (err) {
    return done(err)
  }

  if (!packet.restore) {
    broker.emit('subscribe', packet.subscriptions, client)
  }

  if (packet.messageId) {
    write(client, new SubAck(packet, granted), done)
  } else {
    done()
  }
}

function SubAck (packet, granted) {
  this.cmd = 'suback'
  this.messageId = packet.messageId
  this.granted = granted
}

function nop () {}

module.exports = handleSubscribe
<|endoftext|>


<commit_before>/*
 * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Cloudkick licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var http = require('http');
var url = require('url');

var sprintf = require('extern/sprintf').sprintf;
var log = require('util/log');

var Check = require('health').Check;
var CheckResult = require('health').CheckResult;
var CheckStatus = require('health').CheckStatus;

var config = {
  'name': 'HTTP check',
  'required_arguments': ['url', 'type', 'match_value'],
  'optional_arguments': [],
  'default_values': {},
  'types': {
    STATUS_CODE_MATCH: 0,
    BODY_REGEX_MATCH: 1
  }
};

function HTTPCheck(check_arguments) {
  Check.call(this, config.name, config.required_arguments, config.optional_arguments, config.default_values, check_arguments);
}

// Inherit from the base Check class
HTTPCheck.prototype = new Check();

HTTPCheck.prototype.run = function(callback) {
  log.info('Running HTTP check');

  var self = this;
  var result = new CheckResult();
  var timeout = false;

  var client = http.createClient(self.check_arguments.port, self.check_arguments.host, self.check_arguments.secure);
  var request = client.request('GET', self.check_arguments.path, {'host': self.check_arguments.host});

  function error_handler(exception) {
    result.status = CheckStatus.ERROR;
    result.details = sprintf('Check failed - returned exception: %s', exception);

    self.add_result(result, callback);
  }

  // Register generic error event handlers
  client.on('error', error_handler);
  request.on('error', error_handler);

  client.on('timeout', function() {
    timeout = true;
    // Close the connection, the check status is set in the client end event handler
    client.end();
  });

  client.on('end', function() {
    if (self.last_run_date !== result.date) {
      // If the result hasn't been added yet at this stage this means that the check has failed
      result.status = CheckStatus.ERROR;

      if (timeout === true) {
        result.details = sprintf('Timeout after %s seconds', self.check_arguments.timeout / 1000);
      }
      else {
        result.details = sprintf('Unknown error');
      }

       self.add_result(result, callback);
    }
  });

  // Send the request
  request.end();

  request.on('response', function(response) {
    if (self.check_arguments.type === config.types.STATUS_CODE_MATCH) {
      var status_code = response.statusCode;

      if (status_code === self.check_arguments.match_value) {
        result.status = CheckStatus.SUCCESS;
        result.details = sprintf('Returned status code: %s', status_code);
      }
      else {
        result.status = CheckStatus.ERROR;
        result.details = sprintf('Returned status code:  %s', status_code);
      }

      self.add_result(result, callback);
    }
    else if (self.check_arguments.type === config.types.BODY_REGEX_MATCH) {
      // We only care about the content if it's a body regex match check
      var data_buffer = [];

      response.on('data', function(chunk) {
        data_buffer.push(chunk);
      });

      response.on('end', function() {
        var body = data_buffer.join('');

        if (body.match(self.check_arguments.match_value)) {
          result.status = CheckStatus.SUCCESS;
          result.details = sprintf('The response body matched the regular expression: %s',
                                  self.check_arguments.match_value.toString());
        }
        else {
          result.status = CheckStatus.ERROR;
          result.details = sprintf('The response body didn\'t match the regular expression: %s',
                                   self.check_arguments.match_value.toString());
        }

        self.add_result(result, callback);
      });
    }
  });
};

HTTPCheck.prototype.format_arguments = function(required_arguments, optional_arguments, default_values, check_arguments) {
  var formatted_arguments = Check.prototype.format_arguments.call(this, required_arguments, optional_arguments, default_values,
                                                                  check_arguments);
  var url_object = url.parse(formatted_arguments.url);

  formatted_arguments.host = url_object.hostname;
  formatted_arguments.path = url_object.pathname || '/';

  formatted_arguments.secure = (url_object.protocol === 'https:') || false;
  formatted_arguments.port = url_object.port || (formatted_arguments.secure === true ? 443 : 80);

  if (formatted_arguments.type === config.types.STATUS_CODE_MATCH) {
    formatted_arguments.match_value = parseInt(formatted_arguments.match_value, 10);
  }
  else if (check_arguments.type === config.types.BODY_REGEX_MATCH) {
    formatted_arguments.match_value = new RegExp(formatted_arguments.match_value);
  }

  return formatted_arguments;
};

exports.config = config;
exports.HTTPCheck = HTTPCheck;
<commit_msg>Log more info<commit_after>/*
 * Licensed to Cloudkick, Inc ('Cloudkick') under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * Cloudkick licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var http = require('http');
var url = require('url');

var sprintf = require('extern/sprintf').sprintf;
var log = require('util/log');

var Check = require('health').Check;
var CheckResult = require('health').CheckResult;
var CheckStatus = require('health').CheckStatus;

var config = {
  'name': 'HTTP check',
  'required_arguments': ['url', 'type', 'match_value'],
  'optional_arguments': [],
  'default_values': {},
  'types': {
    STATUS_CODE_MATCH: 0,
    BODY_REGEX_MATCH: 1
  }
};

function HTTPCheck(check_arguments) {
  Check.call(this, config.name, config.required_arguments, config.optional_arguments, config.default_values, check_arguments);
}

// Inherit from the base Check class
HTTPCheck.prototype = new Check();

HTTPCheck.prototype.run = function(callback) {
  log.info(sprintf('Running HTTP check (URL: %s, type: %s, match_value: %s)', this.check_arguments.url, this.check_arguments.type,
                                                                              this.check_arguments.match_value));

  var self = this;
  var result = new CheckResult();
  var timeout = false;

  var client = http.createClient(self.check_arguments.port, self.check_arguments.host, self.check_arguments.secure);
  var request = client.request('GET', self.check_arguments.path, {'host': self.check_arguments.host});

  function error_handler(exception) {
    result.status = CheckStatus.ERROR;
    result.details = sprintf('Check failed - returned exception: %s', exception);

    self.add_result(result, callback);
  }

  // Register generic error event handlers
  client.on('error', error_handler);
  request.on('error', error_handler);

  client.on('timeout', function() {
    timeout = true;
    // Close the connection, the check status is set in the client end event handler
    client.end();
  });

  client.on('end', function() {
    if (self.last_run_date !== result.date) {
      // If the result hasn't been added yet at this stage this means that the check has failed
      result.status = CheckStatus.ERROR;

      if (timeout === true) {
        result.details = sprintf('Timeout after %s seconds', self.check_arguments.timeout / 1000);
      }
      else {
        result.details = sprintf('Unknown error');
      }

       self.add_result(result, callback);
    }
  });

  // Send the request
  request.end();

  request.on('response', function(response) {
    if (self.check_arguments.type === config.types.STATUS_CODE_MATCH) {
      var status_code = response.statusCode;

      if (status_code === self.check_arguments.match_value) {
        result.status = CheckStatus.SUCCESS;
        result.details = sprintf('Returned status code: %s', status_code);
      }
      else {
        result.status = CheckStatus.ERROR;
        result.details = sprintf('Returned status code:  %s', status_code);
      }

      self.add_result(result, callback);
    }
    else if (self.check_arguments.type === config.types.BODY_REGEX_MATCH) {
      // We only care about the content if it's a body regex match check
      var data_buffer = [];

      response.on('data', function(chunk) {
        data_buffer.push(chunk);
      });

      response.on('end', function() {
        var body = data_buffer.join('');

        if (body.match(self.check_arguments.match_value)) {
          result.status = CheckStatus.SUCCESS;
          result.details = sprintf('The response body matched the regular expression: %s',
                                  self.check_arguments.match_value.toString());
        }
        else {
          result.status = CheckStatus.ERROR;
          result.details = sprintf('The response body didn\'t match the regular expression: %s',
                                   self.check_arguments.match_value.toString());
        }

        self.add_result(result, callback);
      });
    }
  });
};

HTTPCheck.prototype.format_arguments = function(required_arguments, optional_arguments, default_values, check_arguments) {
  var formatted_arguments = Check.prototype.format_arguments.call(this, required_arguments, optional_arguments, default_values,
                                                                  check_arguments);
  var url_object = url.parse(formatted_arguments.url);

  formatted_arguments.host = url_object.hostname;
  formatted_arguments.path = url_object.pathname || '/';

  formatted_arguments.secure = (url_object.protocol === 'https:') || false;
  formatted_arguments.port = url_object.port || (formatted_arguments.secure === true ? 443 : 80);

  if (formatted_arguments.type === config.types.STATUS_CODE_MATCH) {
    formatted_arguments.match_value = parseInt(formatted_arguments.match_value, 10);
  }
  else if (check_arguments.type === config.types.BODY_REGEX_MATCH) {
    formatted_arguments.match_value = new RegExp(formatted_arguments.match_value);
  }

  return formatted_arguments;
};

exports.config = config;
exports.HTTPCheck = HTTPCheck;
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2011-2012, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */
var path = require('path'),
    mojito = require('mojito/lib/management/utils'),
    fs = require('fs'),
    Queue = require('buildy').Queue,
    Registry = require('buildy').Registry,
    ShakerCore = require('./core').ShakerCore,
    async = require('async'),
    mkdirp = require('mkdirp'),
    mime = require('mime'),
    utils = require('./utils');

/**
 * Shaker compiler.
 *
 * Compiler takes metadata generated by the core and depending on the environment context
 * will run tasks against the assets. Tasks including concatenating, minifying, and deploying to
 * a variety of locations (local, cdn).
 *
 * Sample application.json:
 *   ...
 *   {
 *       "settings": ["environment:test"],
 *       "shaker": {
 *           "task": "local"
 *       }
 *   },
 *   ...
 *
 * Example:
 * var store = new ResourceStore('./');
 * new Shaker(store).run(function(metadata) {
 *    console.log(metadata);
 * )});
 *
 * @param store {Object} The store object belonging to a Mojito app.
 */
function Shaker(store) {
    this._store = store;

    var config = this._store.getAppConfig(null, 'definition') || {},
        appConfig = config.staticHandling || {};

    this._prefix = '/static';
    if (typeof appConfig.prefix !== 'undefined') {
        this._prefix = appConfig.prefix ? '/' + appConfig.prefix : '';
    }

    var shaker = config.shaker !== undefined ? config.shaker : {};
    this._compile = config.shaker !== undefined; // Shaker config settings are defined in the top level of app config.
    this._task = shaker.task !== undefined ? shaker.task : 'local'; // Task to run when compiling assets
    this._images = shaker.images !== undefined ? shaker.images : false; // Deploy images
    this._parallel = shaker.parallel !== undefined ? shaker.parallel : 20;  // How many tasks the async queue runs
    this._delay = shaker.delay !== undefined ? shaker.delay : 0;    // Add some network delay for slow connections
    this._minify = shaker.minify !== undefined ? shaker.minify : true; // Uglify or not
    this._config = shaker.config !== undefined ? shaker.config : {};    // Config object passed through to task
    this._config.root = this._prefix + '/' + this._store._shortRoot + '/';  // Static prefix for assets generated
}

Shaker.TASKS_DIR = __dirname + '/tasks/';   // Tasks in this directory can be directly referenced in application.json
Shaker.ASSETS_DIR = 'assets/';
Shaker.COMPILED_DIR = Shaker.ASSETS_DIR + 'compiled/';  // Where we write the rollups
Shaker.IMAGES_DIR = Shaker.ASSETS_DIR + 'images/';

Shaker.prototype = {
    /*
     * Start compiling assets.
     *
     * @param callback {function} Callback when compiling is complete.
     */
    run: function(callback) {
        mojito.log('[SHAKER] - Analizying application assets to Shake... ');
        var metadata = new ShakerCore({store: this._store}).shakeAll();

        if (this._compile) {
            this._compileRollups(metadata, callback);
        } else {
            this._rename(metadata);
            this._writeMeta(metadata);
            callback(metadata);
        }
    },

    /*
     * Rename assets from local filename to URL.
     *
     * TODO: should be unnecessary if core keeps mapping of urls -> files
     *
     * @param metadata {Object} Core metadata to augment.
     */
    _rename: function(metadata){
        mojito.log('[SHAKER] - Processing assets for development env.');
        var mojit, action, dim, item, list;

        // Mojit assets
        for (mojit in metadata.mojits) {
            for (action in metadata.mojits[mojit]) {
                for (dim in metadata.mojits[mojit][action].shaken) {
                    for (item in metadata.mojits[mojit][action].shaken[dim]) {
                        list = metadata.mojits[mojit][action].shaken[dim];
                        list[item] = list[item].replace(this._store._root + '/mojits', this._prefix);
                    }
                }
            }
        }

        // App level assets
        for (action in metadata.app) {
            for (dim in metadata.app[action].shaken) {
                for (item in (list = metadata.app[action].shaken[dim])) {
                    var aux = list[item].replace(this._store._root, this._prefix + '/' + this._store._shortRoot);
                    aux = aux.replace(this._prefix + '/' + this._store._shortRoot + '/mojits', this._prefix);
                    list[item] = aux;
                }
            }
        }
    },

    /*
     * The workhorse of compiler. Loads the queue and processes the results. We modify the original metadata
     * object for simplicity.
     *
     * @param metadata {Object} Core metadata to augment.
     * @param callback {function} Callback when rollups are compiled.
     */
    _compileRollups: function(metadata, callback) {
        mojito.log('[SHAKER] - Compiling rollups...');

        var registry = new Registry();
        registry.load(Shaker.TASKS_DIR);

        var self = this;
        var queue = async.queue(function(item, taskCallback) {
            setTimeout(function() {
                var options = {task: self._task, minify: self._minify, config: self._config};
                item.object.push(registry, options, function(err, url) {
                    mojito.log('[SHAKER] - Pushed file ' + url);
                    item.files.push(url);
                    taskCallback();
                });
            }, self._delay);
        }, this._parallel);

        queue.drain = function() {
            self._writeMeta(metadata);
            callback(metadata);
        };

        this._queueRollups(queue, metadata);
    },

    /*
     * Add all assets referenced by metadata to an async queue.
     *
     * @param queue {Object} Queue to append tasks to.
     * @param metadata {Object} Core metadata to look through.
     */
    _queueRollups: function(queue, metadata) {
        var mojit, action, dim, files, name, client, filtered;

        // We need to add images so they can be referenced by relative URLs in the deployed CSS.
        if (this._images) {
            metadata.images.forEach(function(image) {
                queue.push({object: new Image(Shaker.IMAGES_DIR + path.basename(image), image), files: metadata.images});
            });
            metadata.images.length = 0;
        }

        // Mojito core assets
        queue.push({object: new Rollup(Shaker.COMPILED_DIR + 'core_{checksum}.common.js', metadata.core.slice() /* Clone */), files: metadata.core});
        metadata.core.length = 0;

        // Mojit assets
        for (mojit in metadata.mojits) {
            for (action in metadata.mojits[mojit]) {
                // Mojit client assets
                client = metadata.mojits[mojit][action].client;
                if (client.length) {
                    queue.push({object: new ClientRollup(Shaker.COMPILED_DIR + mojit + '_{checksum}.client.js', this._store._root, client.slice() /* Clone */), files: client});
                    client.length = 0;
                }

                for (dim in (files = metadata.mojits[mojit][action].shaken)) {
                    if (files[dim].length) {
                        name = mojit + '_' + action.replace('*', 'default') + '_{checksum}';
                        filtered = this._filterFiles(files[dim]);

                        if (filtered.js.length) {
                            queue.push({object: new Rollup(Shaker.COMPILED_DIR + name + '.common.js', filtered.js), files: files[dim]});
                        }
                        if (filtered.css.length) {
                            queue.push({object: new Rollup(Shaker.COMPILED_DIR + name + '.css', filtered.css), files: files[dim]});
                        }
                        files[dim].length = 0;
                    }
                }
            }
        }

        // App level assets
        for (action in metadata.app) {
            // App client assets
            client = metadata.app[action].client;
            if (client.length) {
                queue.push({object: new ClientRollup(Shaker.COMPILED_DIR + 'app_' + action + '_{checksum}.client.js', this._store._root, client.slice() /* Clone */), files: client});
                client.length = 0;
            }

            for (dim in (files = metadata.app[action].shaken)) {
                if (files[dim].length) {
                    name = 'app_' + action.replace('*', 'default') + '_{checksum}';
                    filtered = this._filterFiles(files[dim]);

                    if (filtered.js.length) {
                        queue.push({object: new Rollup(Shaker.COMPILED_DIR + name + '.common.js', filtered.js), files: files[dim]});
                    }
                    if (filtered.css.length) {
                        queue.push({object: new Rollup(Shaker.COMPILED_DIR + name + '.css', filtered.css), files: files[dim]});
                    }
                    files[dim].length = 0;
                }
            }
        }
    },

    /*
     * Separate JS and CSS files into separate arrays.
     *
     * @param files {array} List of JS/CSS filenames.
     */
    _filterFiles: function(files) {
        var js = [], css = [];

        files.forEach(function(file) {
            var type = mime.lookup(file);

            if (type === 'application/javascript') {
                js.push(file);
            }
            else if (type === 'text/css') {
                css.push(file);
            }
        });

        return {'js': js, 'css': css};
    },

    /*
     * Write the modified metadata.
     *
     * @param metadata {Object} Core metadata to write to file.
     */
    _writeMeta: function(metadata){
        var self = this, aux = "";
        aux += 'YUI.add("shaker/metaMojits", function(Y, NAME) {\n';
        aux += 'YUI.namespace("_mojito._cache.shaker");\n';
        aux += 'YUI._mojito._cache.shaker.meta = \n';
        aux += JSON.stringify(metadata,null,'\t');
        aux += '});';

        mojito.log('[SHAKER] - Writting addon metadata file');
        mkdirp.sync(self._store._root + '/autoload/compiled', 0777 & (~process.umask()));
        fs.writeFileSync(self._store._root + '/autoload/compiled/shaker-meta.common.js', aux);
    }
};

/*
 * Buildy wrapper for images.
 *
 * @param name {string} Name of image to build
 * @param file {string} Filename of image
 */
function Image(name, file) {
    this._name = name;
    this._file = file;
}

Image.prototype = {
    push: function(registry, options, callback) {
        var queue = new Queue(this._name, {registry: registry});

        queue.task('files', [this._file]);
        queue.task('read'); // Read the files into strings for writing

        var config = utils.simpleClone(options.config);
        config.name = this._name;
        queue.task(options.task, config);

        queue.on('taskComplete', function(data) { // queueFailed, queueComplete
            if (data.task.type === options.task) {
                callback(null, data.result);
            }
        });

        queue.run();
    }
};

/*
 * Buildy wrapper for Javascript/CSS.
 *
 * @param name {string} Name of rollup to build
 * @param files {array} Filenames to rollup
 */
function Rollup(name, files) {
    this._name = name;
    this._files = files;
}

Rollup.prototype = {
    push: function(registry, options, callback) {
        var queue = new Queue(this._name, {registry: registry});

        queue.task('files', this._files);
        queue.task('concat');

        if (options.minify) {
            queue.task(mime.lookup(this._name) === 'application/javascript' ? 'jsminify' : 'cssminify');
        }

        var config = utils.simpleClone(options.config);
        config.name = this._name;
        queue.task(options.task, config);

        queue.on('taskComplete', function(data) {
            if (data.task.type === options.task) {
                callback(null, data.result);
            }
        });

        queue.run();
    }
};

/*
 * Buildy wrapper for templates.
 *
 * @param name {string} Name of template to build
 * @param files {array} Filenames to rollup
 */
function ClientRollup(name, base, files) {
    this._name = name;
    this._base = base;
    this._files = files;
}

ClientRollup.prototype = {
    push: function(registry, options, callback) {
        var queue = new Queue(this._name, {registry: registry}),
            self = this;
        queue.task('files', this._files);

        queue.task('concatcb', {callback: function(filename, content) {
            if (mime.lookup(filename) == 'text/html') {
                var parts = filename.substring(self._base.length + 1).split('/');
                    mojit = parts[1],
                    action = parts[3].substring(0, parts[3].indexOf('.'));
                    json = JSON.stringify(content);

                content = 'YUI.add("views/' + mojit + '/' + action + '", function(Y, NAME) {\n';
                content += '\tYUI.namespace("_mojito._cache.compiled.'+ mojit +'.views");\n';
                content += '\tYUI._mojito._cache.compiled.'+ mojit +'.views.'+ action + ' = ' +  json + ';\n';
                content += '});';
            }

            return content;
        }});

        if (options.minify) {
            queue.task('jsminify');
        }

        var config = utils.simpleClone(options.config);
        config.name = this._name;
        queue.task(options.task, config);

        queue.on('taskComplete', function(data) {
            if (data.task.type === options.task) {
                callback(null, data.result);
            }
        });

        queue.run();
    }
};

exports.Shaker = Shaker;
<commit_msg>Minor update <commit_after>/*
 * Copyright (c) 2011-2012, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */
var path = require('path'),
    mojito = require('mojito/lib/management/utils'),
    fs = require('fs'),
    Queue = require('buildy').Queue,
    Registry = require('buildy').Registry,
    async = require('async'),
    mkdirp = require('mkdirp'),
    mime = require('mime'),
    ShakerCore = require('./core').ShakerCore,
    utils = require('./utils');

/**
 * Shaker compiler.
 *
 * Compiler takes metadata generated by the core and depending on the environment context
 * will run tasks against the assets. Tasks including concatenating, minifying, and deploying to
 * a variety of locations (local, cdn).
 *
 * Sample application.json:
 *   ...
 *   {
 *       "settings": ["environment:test"],
 *       "shaker": {
 *           "task": "local"
 *       }
 *   },
 *   ...
 *
 * Example:
 * var store = new ResourceStore('./');
 * new Shaker(store).run(function(metadata) {
 *    console.log(metadata);
 * )});
 *
 * @param store {Object} The store object belonging to a Mojito app.
 */
function Shaker(store) {
    this._store = store;

    var config = this._store.getAppConfig(null, 'definition') || {},
        appConfig = config.staticHandling || {};

    this._prefix = '/static';
    if (typeof appConfig.prefix !== 'undefined') {
        this._prefix = appConfig.prefix ? '/' + appConfig.prefix : '';
    }

    var shaker = config.shaker !== undefined ? config.shaker : {};
    this._compile = config.shaker !== undefined; // Shaker config settings are defined in the top level of app config.
    this._task = shaker.task !== undefined ? shaker.task : 'local'; // Task to run when compiling assets
    this._images = shaker.images !== undefined ? shaker.images : false; // Deploy images
    this._parallel = shaker.parallel !== undefined ? shaker.parallel : 20;  // How many tasks the async queue runs
    this._delay = shaker.delay !== undefined ? shaker.delay : 0;    // Add some network delay for slow connections
    this._minify = shaker.minify !== undefined ? shaker.minify : true; // Uglify or not
    this._config = shaker.config !== undefined ? shaker.config : {};    // Config object passed through to task
    this._config.root = this._prefix + '/' + this._store._shortRoot + '/';  // Static prefix for assets generated
}

Shaker.TASKS_DIR = __dirname + '/tasks/';   // Tasks in this directory can be directly referenced in application.json
Shaker.ASSETS_DIR = 'assets/';
Shaker.COMPILED_DIR = Shaker.ASSETS_DIR + 'compiled/';  // Where we write the rollups
Shaker.IMAGES_DIR = Shaker.ASSETS_DIR + 'images/';

Shaker.prototype = {
    /*
     * Start compiling assets.
     *
     * @param callback {function} Callback when compiling is complete.
     */
    run: function(callback) {
        mojito.log('[SHAKER] - Analizying application assets to Shake... ');
        var metadata = new ShakerCore({store: this._store}).shakeAll();

        if (this._compile) {
            this._compileRollups(metadata, callback);
        } else {
            this._rename(metadata);
            this._writeMeta(metadata);
            callback(metadata);
        }
    },

    /*
     * Rename assets from local filename to URL.
     *
     * TODO: should be unnecessary if core keeps mapping of urls -> files
     *
     * @param metadata {Object} Core metadata to augment.
     */
    _rename: function(metadata){
        mojito.log('[SHAKER] - Processing assets for development env.');
        var mojit, action, dim, item, list;

        // Mojit assets
        for (mojit in metadata.mojits) {
            for (action in metadata.mojits[mojit]) {
                for (dim in metadata.mojits[mojit][action].shaken) {
                    for (item in metadata.mojits[mojit][action].shaken[dim]) {
                        list = metadata.mojits[mojit][action].shaken[dim];
                        list[item] = list[item].replace(this._store._root + '/mojits', this._prefix);
                    }
                }
            }
        }

        // App level assets
        for (action in metadata.app) {
            for (dim in metadata.app[action].shaken) {
                for (item in (list = metadata.app[action].shaken[dim])) {
                    var aux = list[item].replace(this._store._root, this._prefix + '/' + this._store._shortRoot);
                    aux = aux.replace(this._prefix + '/' + this._store._shortRoot + '/mojits', this._prefix);
                    list[item] = aux;
                }
            }
        }
    },

    /*
     * The workhorse of compiler. Loads the queue and processes the results. We modify the original metadata
     * object for simplicity.
     *
     * @param metadata {Object} Core metadata to augment.
     * @param callback {function} Callback when rollups are compiled.
     */
    _compileRollups: function(metadata, callback) {
        mojito.log('[SHAKER] - Compiling rollups...');

        var registry = new Registry();
        registry.load(Shaker.TASKS_DIR);

        var self = this;
        var queue = async.queue(function(item, taskCallback) {
            setTimeout(function() {
                var options = {task: self._task, minify: self._minify, config: self._config};
                item.object.push(registry, options, function(err, url) {
                    mojito.log('[SHAKER] - Pushed file ' + url);
                    item.files.push(url);
                    taskCallback();
                });
            }, self._delay);
        }, this._parallel);

        queue.drain = function() {
            self._writeMeta(metadata);
            callback(metadata);
        };

        this._queueRollups(queue, metadata);
    },

    /*
     * Add all assets referenced by metadata to an async queue.
     *
     * @param queue {Object} Queue to append tasks to.
     * @param metadata {Object} Core metadata to look through.
     */
    _queueRollups: function(queue, metadata) {
        var mojit, action, dim, files, name, client, filtered;

        // We need to add images so they can be referenced by relative URLs in the deployed CSS.
        if (this._images) {
            metadata.images.forEach(function(image) {
                queue.push({object: new Image(Shaker.IMAGES_DIR + path.basename(image), image), files: metadata.images});
            });
            metadata.images.length = 0;
        }

        // Mojito core assets
        queue.push({object: new Rollup(Shaker.COMPILED_DIR + 'core_{checksum}.common.js', metadata.core.slice() /* Clone */), files: metadata.core});
        metadata.core.length = 0;

        // Mojit assets
        for (mojit in metadata.mojits) {
            for (action in metadata.mojits[mojit]) {
                // Mojit client assets
                client = metadata.mojits[mojit][action].client;
                if (client.length) {
                    queue.push({object: new ClientRollup(Shaker.COMPILED_DIR + mojit + '_{checksum}.client.js', this._store._root, client.slice() /* Clone */), files: client});
                    client.length = 0;
                }

                for (dim in (files = metadata.mojits[mojit][action].shaken)) {
                    if (files[dim].length) {
                        name = mojit + '_' + action.replace('*', 'default') + '_{checksum}';
                        filtered = this._filterFiles(files[dim]);

                        if (filtered.js.length) {
                            queue.push({object: new Rollup(Shaker.COMPILED_DIR + name + '.common.js', filtered.js), files: files[dim]});
                        }
                        if (filtered.css.length) {
                            queue.push({object: new Rollup(Shaker.COMPILED_DIR + name + '.css', filtered.css), files: files[dim]});
                        }
                        files[dim].length = 0;
                    }
                }
            }
        }

        // App level assets
        for (action in metadata.app) {
            // App client assets
            client = metadata.app[action].client;
            if (client.length) {
                queue.push({object: new ClientRollup(Shaker.COMPILED_DIR + 'app_' + action + '_{checksum}.client.js', this._store._root, client.slice() /* Clone */), files: client});
                client.length = 0;
            }

            for (dim in (files = metadata.app[action].shaken)) {
                if (files[dim].length) {
                    name = 'app_' + action.replace('*', 'default') + '_{checksum}';
                    filtered = this._filterFiles(files[dim]);

                    if (filtered.js.length) {
                        queue.push({object: new Rollup(Shaker.COMPILED_DIR + name + '.common.js', filtered.js), files: files[dim]});
                    }
                    if (filtered.css.length) {
                        queue.push({object: new Rollup(Shaker.COMPILED_DIR + name + '.css', filtered.css), files: files[dim]});
                    }
                    files[dim].length = 0;
                }
            }
        }
    },

    /*
     * Separate JS and CSS files into separate arrays.
     *
     * @param files {array} List of JS/CSS filenames.
     */
    _filterFiles: function(files) {
        var js = [], css = [];

        files.forEach(function(file) {
            var type = mime.lookup(file);

            if (type === 'application/javascript') {
                js.push(file);
            }
            else if (type === 'text/css') {
                css.push(file);
            }
        });

        return {'js': js, 'css': css};
    },

    /*
     * Write the modified metadata.
     *
     * @param metadata {Object} Core metadata to write to file.
     */
    _writeMeta: function(metadata){
        var self = this, aux = "";
        aux += 'YUI.add("shaker/metaMojits", function(Y, NAME) {\n';
        aux += 'YUI.namespace("_mojito._cache.shaker");\n';
        aux += 'YUI._mojito._cache.shaker.meta = \n';
        aux += JSON.stringify(metadata,null,'\t');
        aux += '});';

        mojito.log('[SHAKER] - Writting addon metadata file');
        mkdirp.sync(self._store._root + '/autoload/compiled', 0777 & (~process.umask()));
        fs.writeFileSync(self._store._root + '/autoload/compiled/shaker-meta.common.js', aux);
    }
};

/*
 * Buildy wrapper for images.
 *
 * @param name {string} Name of image to build
 * @param file {string} Filename of image
 */
function Image(name, file) {
    this._name = name;
    this._file = file;
}

Image.prototype = {
    push: function(registry, options, callback) {
        var queue = new Queue(this._name, {registry: registry});

        queue.task('files', [this._file]);
        queue.task('read'); // Read the files into strings for writing

        var config = utils.simpleClone(options.config);
        config.name = this._name;
        queue.task(options.task, config);

        queue.on('taskComplete', function(data) { // queueFailed, queueComplete
            if (data.task.type === options.task) {
                callback(null, data.result);
            }
        });

        queue.run();
    }
};

/*
 * Buildy wrapper for Javascript/CSS.
 *
 * @param name {string} Name of rollup to build
 * @param files {array} Filenames to rollup
 */
function Rollup(name, files) {
    this._name = name;
    this._files = files;
}

Rollup.prototype = {
    push: function(registry, options, callback) {
        var queue = new Queue(this._name, {registry: registry});

        queue.task('files', this._files);
        queue.task('concat');

        if (options.minify) {
            queue.task(mime.lookup(this._name) === 'application/javascript' ? 'jsminify' : 'cssminify');
        }

        var config = utils.simpleClone(options.config);
        config.name = this._name;
        queue.task(options.task, config);

        queue.on('taskComplete', function(data) {
            if (data.task.type === options.task) {
                callback(null, data.result);
            }
        });

        queue.run();
    }
};

/*
 * Buildy wrapper for templates.
 *
 * @param name {string} Name of template to build
 * @param files {array} Filenames to rollup
 */
function ClientRollup(name, base, files) {
    this._name = name;
    this._base = base;
    this._files = files;
}

ClientRollup.prototype = {
    push: function(registry, options, callback) {
        var queue = new Queue(this._name, {registry: registry}),
            self = this;
        queue.task('files', this._files);

        queue.task('concatcb', {callback: function(filename, content) {
            if (mime.lookup(filename) == 'text/html') {
                var parts = filename.substring(self._base.length + 1).split('/');
                    mojit = parts[1],
                    action = parts[3].substring(0, parts[3].indexOf('.'));
                    json = JSON.stringify(content);

                content = 'YUI.add("views/' + mojit + '/' + action + '", function(Y, NAME) {\n';
                content += '\tYUI.namespace("_mojito._cache.compiled.'+ mojit +'.views");\n';
                content += '\tYUI._mojito._cache.compiled.'+ mojit +'.views.'+ action + ' = ' +  json + ';\n';
                content += '});';
            }

            return content;
        }});

        if (options.minify) {
            queue.task('jsminify');
        }

        var config = utils.simpleClone(options.config);
        config.name = this._name;
        queue.task(options.task, config);

        queue.on('taskComplete', function(data) {
            if (data.task.type === options.task) {
                callback(null, data.result);
            }
        });

        queue.run();
    }
};

exports.Shaker = Shaker;
<|endoftext|>