<commit_before>/**
* Copyright (c) Microsoft.  All rights reserved.
*
* Licensed 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.
*/

'use strict';

//
// Extensions to Commander to support nested commands / help system
//
var _ = require('underscore');
var commander = require('commander');
var fs = require('fs');
var path = require('path');
var util = require('util');

var utils = require('./utils');

function ExtendedCommand(name) {
  ExtendedCommand['super_'].call(this, name);
}

util.inherits(ExtendedCommand, commander.Command);

_.extend(ExtendedCommand.prototype, {

  //////////////////////////////
  // override help subsystem

  fullName: function () {
    var name = this.name;
    var scan = this.parent;
    while (scan.parent !== undefined) {
      name = scan.name + ' ' + name;
      scan = scan.parent;
    }

    return name;
  },

  usage: function (str) {
    var ret;

    if (str) {
      ret = commander.Command.prototype.usage.call(this, str);
    } else {
      ret = commander.Command.prototype.usage.call(this);
      ret = ret.replace(/,/g,' ');
    }

    return ret;
  },

  helpInformation: function() {
    var self = this;

    if (!self.parent) {
      var args = process.argv.slice(0, 2);
      var raw = self.normalize(process.argv.slice(2));

      var packagePath = path.join(__dirname, '../../package.json');
      var packageInfo = JSON.parse(fs.readFileSync(packagePath));

      if (raw.indexOf('-v') >= 0) {
        console.log(packageInfo.version);
      } else if (raw.indexOf('--version') >= 0) {
        console.log(util.format('%s (node: %s)', packageInfo.version, process.versions.node));
      } else {
        self.setupCommand(args, raw, true);

        if (this.output.format().log === 'on') {
          this.output.info('         _    _____   _ ___ ___'.cyan);
          this.output.info('        /_\\  |_  / | | | _ \\ __|'.cyan);
          this.output.info('  _ ___'.grey + '/ _ \\'.cyan + '__'.grey + '/ /| |_| |   / _|'.cyan + '___ _ _'.grey);
          this.output.info('(___  '.grey + '/_/ \\_\\/___|\\___/|_|_\\___|'.cyan + ' _____)'.grey);
          this.output.info('   (_______ _ _)         _ ______ _)_ _ '.grey);
          this.output.info('          (______________ _ )   (___ _ _)'.grey);
          this.output.info('');
        }

        this.output.info('Windows Azure: Microsoft\'s Cloud Platform');
        this.output.info('');
        this.output.info('Tool version', packageInfo.version);

        self.helpCommands();
        self.helpCategoriesSummary(self.showMore());
        self.helpOptions();
      }
    } else {
      this.output.help(self.description());
      this.output.help('');
      this.output.help('Usage:', self.fullName() + ' ' + self.usage());
      self.helpOptions();
    }

    return '';
  },

  showMore: function () {
    var raw = this.normalize(process.argv.slice(2));
    return raw.indexOf('--help') >= 0 || raw.indexOf('-h') >= 0;
  },

  categoryHelpInformation: function() {
    var self = this;

    this.output.help(self.description());
    self.helpCommands();
    self.helpCategories(-1) ;
    self.helpOptions();

    return '';
  },

  commandHelpInformation: function() {
    var self = this;

    if (self._detailedDescription) {
      this.output.help(self.detailedDescription());
    } else {
      this.output.help(self.description());
    }

    this.output.help('');
    this.output.help('Usage:', self.fullName() + ' ' + self.usage());

    self.helpOptions();

    return '';
  },

  helpJSON: function() {
    var result = {};

    if (this.categories && Object.keys(this.categories).length > 0) {
      result.categories = {};

      for (var name in this.categories) {
        result.categories[name] = this.categories[name].helpJSON();
      }
    }

    if (this.commands && this.commands.length > 0) {
      result.commands = [];

      this.commands.forEach(function (cmd) {
        var command = {
          name: cmd.fullName(),
          description: cmd.description(),
          options: cmd.options,
          usage: cmd.usage()
        };

        result.commands.push(command);
      });
    }

    return result;
  },

  helpCategories: function(levels) {
    for (var name in this.categories) {
      var cat = this.categories[name];

      this.output.help('');

      this.output.help(cat.description().cyan);

      if (levels === -1 || levels > 0) {
        for (var index in cat.commands) {
          var cmd = cat.commands[index];
          this.output.help(' ', cmd.fullName() + ' ' + cmd.usage());
        }

        cat.helpCategories(levels !== -1 ? --levels : -1);
      } else {
        this.output.help(' ', cat.fullName());
      }
    }
  },

  helpCategoriesSummary: function(showMore) {
    var self = this;
    var categories = [];
    function scan(parent, levels, each) {
      for (var name in parent.categories) {
        var cat = parent.categories[name];

        each(cat);

        if (levels === -1 || levels > 0) {
          scan(cat, levels !== -1 ? --levels : -1, each);
        }
      }
    }

    scan(this, showMore ? -1 : 0, function (cat) { categories.push(cat); });
    var maxLength = 14;

    // Sort categories by alphabetical order
    categories.sort(function (a, b) {
      return (a.fullName() <  b.fullName()) ? -1 : (a.fullName() >  b.fullName()) ? 1 : 0;
    });

    categories.forEach(function (cat) {
      if (maxLength < cat.fullName().length)
        maxLength = cat.fullName().length;
    });

    self.output.help('');
    self.output.help('Commands:');
    categories.forEach(function (cat) {
      var name = cat.fullName();
      while (name.length < maxLength) {
        name += ' ';
      }

      self.output.help('  ' + name + ' ' + cat.description().cyan);
    });
  },

  helpCommands: function() {
    var self = this;

    this.commands.forEach(function (cmd) {
      self.output.help('');

      self.output.help(cmd.description().cyan);
      self.output.help(' ', cmd.fullName() + ' ' + cmd.usage());
    });
  },

  helpOptions: function(cmdExtra) {
    var self = this;

    var revert = self.options;
    if (cmdExtra) {
      self.options = self.options.concat(cmdExtra.options);
    }

    this.output.help('');
    this.output.help('Options:');
    self.optionHelp().split('\n').forEach(function (line) { self.output.help(' ', line); });
    self.options = revert;
  },

  option: function(flags, description, fn, defaultValue){
    var self = this;
    var option = new commander.Option(flags, description);
    var oname = option.name();
    var name = utils.camelcase(oname);

    if (!self.optionValues) {
      self.optionValues = {};
    }

    // default as 3rd arg
    if ('function' !== typeof fn) {
      defaultValue = fn;
      fn = null;
    }

    // preassign default value only for --no-*, [optional], or <required>
    if (false === option.bool || option.optional || option.required) {
      // when --no-* we make sure default is true
      if (false === option.bool) defaultValue = true;
      // preassign only if we have a default
      if (undefined !== defaultValue) self.optionValues[name] = defaultValue;
    }

    // register the option
    this.options.push(option);

    // when it's passed assign the value
    // and conditionally invoke the callback
    this.on(oname, function (val) {
      // coercion
      if (val && fn) val = fn(val);

      // unassigned or bool
      if ('boolean' === typeof self.optionValues[name] || 'undefined' === typeof self.optionValues[name]) {
        // if no value, bool true, and we have a default, then use it!
        if (!val) {
          self.optionValues[name] = option.bool ? defaultValue || true : false;
        } else {
          self.optionValues[name] = val;
        }
      } else if (val) {
        // reassign
        self.optionValues[name] = val;
      }
    });

    return this;
  }
});

module.exports = ExtendedCommand;
<commit_msg>Fixed typo that disabled the ascii art<commit_after>/**
* Copyright (c) Microsoft.  All rights reserved.
*
* Licensed 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.
*/

'use strict';

//
// Extensions to Commander to support nested commands / help system
//
var _ = require('underscore');
var commander = require('commander');
var fs = require('fs');
var path = require('path');
var util = require('util');

var utils = require('./utils');

function ExtendedCommand(name) {
  ExtendedCommand['super_'].call(this, name);
}

util.inherits(ExtendedCommand, commander.Command);

_.extend(ExtendedCommand.prototype, {

  //////////////////////////////
  // override help subsystem

  fullName: function () {
    var name = this.name;
    var scan = this.parent;
    while (scan.parent !== undefined) {
      name = scan.name + ' ' + name;
      scan = scan.parent;
    }

    return name;
  },

  usage: function (str) {
    var ret;

    if (str) {
      ret = commander.Command.prototype.usage.call(this, str);
    } else {
      ret = commander.Command.prototype.usage.call(this);
      ret = ret.replace(/,/g,' ');
    }

    return ret;
  },

  helpInformation: function() {
    var self = this;

    if (!self.parent) {
      var args = process.argv.slice(0, 2);
      var raw = self.normalize(process.argv.slice(2));

      var packagePath = path.join(__dirname, '../../package.json');
      var packageInfo = JSON.parse(fs.readFileSync(packagePath));

      if (raw.indexOf('-v') >= 0) {
        console.log(packageInfo.version);
      } else if (raw.indexOf('--version') >= 0) {
        console.log(util.format('%s (node: %s)', packageInfo.version, process.versions.node));
      } else {
        self.setupCommand(args, raw, true);

        if (this.output.format().logo === 'on') {
          this.output.info('         _    _____   _ ___ ___'.cyan);
          this.output.info('        /_\\  |_  / | | | _ \\ __|'.cyan);
          this.output.info('  _ ___'.grey + '/ _ \\'.cyan + '__'.grey + '/ /| |_| |   / _|'.cyan + '___ _ _'.grey);
          this.output.info('(___  '.grey + '/_/ \\_\\/___|\\___/|_|_\\___|'.cyan + ' _____)'.grey);
          this.output.info('   (_______ _ _)         _ ______ _)_ _ '.grey);
          this.output.info('          (______________ _ )   (___ _ _)'.grey);
          this.output.info('');
        }

        this.output.info('Windows Azure: Microsoft\'s Cloud Platform');
        this.output.info('');
        this.output.info('Tool version', packageInfo.version);

        self.helpCommands();
        self.helpCategoriesSummary(self.showMore());
        self.helpOptions();
      }
    } else {
      this.output.help(self.description());
      this.output.help('');
      this.output.help('Usage:', self.fullName() + ' ' + self.usage());
      self.helpOptions();
    }

    return '';
  },

  showMore: function () {
    var raw = this.normalize(process.argv.slice(2));
    return raw.indexOf('--help') >= 0 || raw.indexOf('-h') >= 0;
  },

  categoryHelpInformation: function() {
    var self = this;

    this.output.help(self.description());
    self.helpCommands();
    self.helpCategories(-1) ;
    self.helpOptions();

    return '';
  },

  commandHelpInformation: function() {
    var self = this;

    if (self._detailedDescription) {
      this.output.help(self.detailedDescription());
    } else {
      this.output.help(self.description());
    }

    this.output.help('');
    this.output.help('Usage:', self.fullName() + ' ' + self.usage());

    self.helpOptions();

    return '';
  },

  helpJSON: function() {
    var result = {};

    if (this.categories && Object.keys(this.categories).length > 0) {
      result.categories = {};

      for (var name in this.categories) {
        result.categories[name] = this.categories[name].helpJSON();
      }
    }

    if (this.commands && this.commands.length > 0) {
      result.commands = [];

      this.commands.forEach(function (cmd) {
        var command = {
          name: cmd.fullName(),
          description: cmd.description(),
          options: cmd.options,
          usage: cmd.usage()
        };

        result.commands.push(command);
      });
    }

    return result;
  },

  helpCategories: function(levels) {
    for (var name in this.categories) {
      var cat = this.categories[name];

      this.output.help('');

      this.output.help(cat.description().cyan);

      if (levels === -1 || levels > 0) {
        for (var index in cat.commands) {
          var cmd = cat.commands[index];
          this.output.help(' ', cmd.fullName() + ' ' + cmd.usage());
        }

        cat.helpCategories(levels !== -1 ? --levels : -1);
      } else {
        this.output.help(' ', cat.fullName());
      }
    }
  },

  helpCategoriesSummary: function(showMore) {
    var self = this;
    var categories = [];
    function scan(parent, levels, each) {
      for (var name in parent.categories) {
        var cat = parent.categories[name];

        each(cat);

        if (levels === -1 || levels > 0) {
          scan(cat, levels !== -1 ? --levels : -1, each);
        }
      }
    }

    scan(this, showMore ? -1 : 0, function (cat) { categories.push(cat); });
    var maxLength = 14;

    // Sort categories by alphabetical order
    categories.sort(function (a, b) {
      return (a.fullName() <  b.fullName()) ? -1 : (a.fullName() >  b.fullName()) ? 1 : 0;
    });

    categories.forEach(function (cat) {
      if (maxLength < cat.fullName().length)
        maxLength = cat.fullName().length;
    });

    self.output.help('');
    self.output.help('Commands:');
    categories.forEach(function (cat) {
      var name = cat.fullName();
      while (name.length < maxLength) {
        name += ' ';
      }

      self.output.help('  ' + name + ' ' + cat.description().cyan);
    });
  },

  helpCommands: function() {
    var self = this;

    this.commands.forEach(function (cmd) {
      self.output.help('');

      self.output.help(cmd.description().cyan);
      self.output.help(' ', cmd.fullName() + ' ' + cmd.usage());
    });
  },

  helpOptions: function(cmdExtra) {
    var self = this;

    var revert = self.options;
    if (cmdExtra) {
      self.options = self.options.concat(cmdExtra.options);
    }

    this.output.help('');
    this.output.help('Options:');
    self.optionHelp().split('\n').forEach(function (line) { self.output.help(' ', line); });
    self.options = revert;
  },

  option: function(flags, description, fn, defaultValue){
    var self = this;
    var option = new commander.Option(flags, description);
    var oname = option.name();
    var name = utils.camelcase(oname);

    if (!self.optionValues) {
      self.optionValues = {};
    }

    // default as 3rd arg
    if ('function' !== typeof fn) {
      defaultValue = fn;
      fn = null;
    }

    // preassign default value only for --no-*, [optional], or <required>
    if (false === option.bool || option.optional || option.required) {
      // when --no-* we make sure default is true
      if (false === option.bool) defaultValue = true;
      // preassign only if we have a default
      if (undefined !== defaultValue) self.optionValues[name] = defaultValue;
    }

    // register the option
    this.options.push(option);

    // when it's passed assign the value
    // and conditionally invoke the callback
    this.on(oname, function (val) {
      // coercion
      if (val && fn) val = fn(val);

      // unassigned or bool
      if ('boolean' === typeof self.optionValues[name] || 'undefined' === typeof self.optionValues[name]) {
        // if no value, bool true, and we have a default, then use it!
        if (!val) {
          self.optionValues[name] = option.bool ? defaultValue || true : false;
        } else {
          self.optionValues[name] = val;
        }
      } else if (val) {
        // reassign
        self.optionValues[name] = val;
      }
    });

    return this;
  }
});

module.exports = ExtendedCommand;
<|endoftext|>


<commit_before>/**
 * The Router class is responsible for all url changes and opening the corresponding views.
 */
window.Router = Backbone.Router.extend({

    /** Maps routes to functions */
    routes: {
        "about":            "about",            // #about
        "login":            "login",            // #login
        "logout":           "logout",           // #logout
        "register":         "register",         // #register
        "settings":         "settings",         // #settings
        "import":           "import",           // #import
        "admin":            "admin",            // #admin
        "admin/user":       "adminNewUser",     // #admin/user
        "admin/user/:id":   "adminEditUser",    // #admin/user/42
        "billing":          "billing",          // #billing
        "billing/user/:id": "billing",          // #billing/42
        "route/:id":        "request"           // #route/42
    },

    /**
     * Constructor for the Router class. Initializes the user.
     */
    initialize: function() {
        this.user = new User();
        this.user.bind('login', _.bind(this.onLogin, this));

        this.lastURL = window.location.hash.substr(1);
        if (this.lastURL === 'login')
            this.lastURL = null;
        window.location.hash = '';
    },

    /**
     * Get the informations of the server and display accordingly.
     */
    initServer: function() {
        var that = this;
        var loadingView = new LoadingView($._('Loading server information')).render();

        window.server.getServerInfo(function() {
            if (window.server.isPublic()) {
                window.body.topbar.hideNavigation();
                that.onLogin(true);
            } else if (!that.user.isLoggedIn()) {
                that.navigate('login', {trigger: true});
            }
            // set shortest path to default
            window.algview.setSelectedAlgorithm('sp');
            loadingView.remove();
        });
    },

    about: function () {
        new AboutView().render();
    },

    /**
     * Display the login view.
     */
    login: function() {
        if (!window.server.isPublic())
            new LoginView().render();
    },

    /**
     * Log the user out and display the login view.
     */
    logout: function() {
        if (this.user.isLoggedIn()) {
            window.map.resetRoute();
            window.map.resetMarkers();
            window.markList.reset();
            this.user.logout();
            this.navigate('login', {trigger: true});
        }
    },

    /**
     * Display the register view.
     */
    register: function() {
        if (!window.server.isPublic() && !this.user.isLoggedIn())
            new RegisterView().render();
    },

    /**
     * Display the settings view.
     */
    settings: function() {
        if (!window.server.isPublic() && this.user.isLoggedIn())
            new UserDialogView().render();
    },

    /**
     * Display the im-/export view.
     */
    "import": function() {
        if (!window.server.isPublic() && this.user.isLoggedIn()) {
            if (Modernizr.file) {
                new ImExportView().render();
            } else {
                new MessageView({
                    title: $._('Error!'),
                    message: $._('Your browser doesn\'t support the HTML 5 File API.
 To use the Im-/Export functionality, please upgrade your browser.')
                }).render();
            }
        }
    },

    /**
     * Display the admin view.
     */
    admin: function() {
        if (!window.server.isPublic() && this.user.get("admin"))
            this.adminView = new AdminView({remove: _.bind(this.onAdminRemove, this)}).render();
    },

    /**
     * Change the content of the admin view to the new user view.
     */
    adminNewUser: function() {
        if (!window.server.isPublic() && this.user.get("admin")) {
            if (_.isNull(this.adminView) || _.isUndefined(this.adminView))
                this.admin();

            this.adminView.setContent(new UserView().render());
        }
    },

    /**
     * Change the content of the admin view to the edit user view.
     *
     * @param id The id of the user to edit
     */
    adminEditUser: function(id) {
        if (!window.server.isPublic() && this.user.get("admin")) {
            if (_.isNull(this.adminView) || _.isUndefined(this.adminView))
                this.admin();

            // Use sessionStorage if available
            if (Modernizr.sessionstorage && !_.isNull(sessionStorage.getItem('edit-user'))) {
                var user = new User();
                user.set(JSON.parse(sessionStorage.getItem('edit-user')));
                this.adminView.setContent(new UserView({model: user}).render());
                sessionStorage.removeItem('edit-user');
                return;
            }

            this.loadingView = new LoadingView($._('Loading user information')).render();

            var that = this;
            var model = new User().load(id, function () {
                that.adminView.setContent(new UserView({model: this}).render());
                that.loadingView.remove();
            });
        }
    },

    /**
     * Display the billing view.
     */
    billing: function(id) {
        if (!window.server.isPublic())
            this.billingView = new BillingView({remove: _.bind(this.onBillingRemove, this)}).render(id);
    },

    /**
     * Display an old request by loading it from the server.
     *
     * @param id The id of the old request
     */
    request: function(id) {
        if (_.isNaN(id) || id <= 0)
            return;

        window.api.getResponse({
            id: id,
            callback: function(text, success) {
                this.loadingView = new LoadingView($._('Loading route information')).render();
                if (success) {
                    window.map.resetRoute();
                    window.map.resetMarkers();
                    window.map.drawMarkersAndRoute(text);
                }
                this.loadingView.remove();
            }
        });
    },

    onAdminRemove: function () {
        this.adminView = null;
    },

    onBillingRemove: function () {
        this.billingView = null;
    },

    /**
     * Navigate to the requested url after a successful login.
     */
    onLogin: function (success) {
        if (success && !_.isUndefined(this.lastURL) && !_.isNull(this.lastURL)) {
            this.navigate(this.lastURL, {trigger: true});
            this.lastURL = null;
        }
    }

});
<commit_msg>add spot search via address bar: add '#locate/$' to url<commit_after>/**
 * The Router class is responsible for all url changes and opening the corresponding views.
 */
window.Router = Backbone.Router.extend({

    /** Maps routes to functions */
    routes: {
        "about":            "about",            // #about
        "login":            "login",            // #login
        "logout":           "logout",           // #logout
        "register":         "register",         // #register
        "settings":         "settings",         // #settings
        "import":           "import",           // #import
        "admin":            "admin",            // #admin
        "admin/user":       "adminNewUser",     // #admin/user
        "admin/user/:id":   "adminEditUser",    // #admin/user/42
        "billing":          "billing",          // #billing
        "billing/user/:id": "billing",          // #billing/42
        "route/:id":        "request",          // #route/42
        "locate/:q":        "locate"            // #locate/Universitätsstr.38,Stuttgart
    },

    /**
     * Constructor for the Router class. Initializes the user.
     */
    initialize: function() {
        this.user = new User();
        this.user.bind('login', _.bind(this.onLogin, this));

        this.lastURL = window.location.hash.substr(1);
        if (this.lastURL === 'login')
            this.lastURL = null;
        window.location.hash = '';
    },

    /**
     * Get the informations of the server and display accordingly.
     */
    initServer: function() {
        var that = this;
        var loadingView = new LoadingView($._('Loading server information')).render();

        window.server.getServerInfo(function() {
            if (window.server.isPublic()) {
                window.body.topbar.hideNavigation();
                that.onLogin(true);
            } else if (!that.user.isLoggedIn()) {
                that.navigate('login', {trigger: true});
            }
            // set shortest path to default
            window.algview.setSelectedAlgorithm('sp');
            loadingView.remove();
        });
    },

    about: function () {
        new AboutView().render();
    },

    /**
     * Display the login view.
     */
    login: function() {
        if (!window.server.isPublic())
            new LoginView().render();
    },

    /**
     * Log the user out and display the login view.
     */
    logout: function() {
        if (this.user.isLoggedIn()) {
            window.map.resetRoute();
            window.map.resetMarkers();
            window.markList.reset();
            this.user.logout();
            this.navigate('login', {trigger: true});
        }
    },

    /**
     * Display the register view.
     */
    register: function() {
        if (!window.server.isPublic() && !this.user.isLoggedIn())
            new RegisterView().render();
    },

    /**
     * Display the settings view.
     */
    settings: function() {
        if (!window.server.isPublic() && this.user.isLoggedIn())
            new UserDialogView().render();
    },

    /**
     * Display the im-/export view.
     */
    "import": function() {
        if (!window.server.isPublic() && this.user.isLoggedIn()) {
            if (Modernizr.file) {
                new ImExportView().render();
            } else {
                new MessageView({
                    title: $._('Error!'),
                    message: $._('Your browser doesn\'t support the HTML 5 File API.
 To use the Im-/Export functionality, please upgrade your browser.')
                }).render();
            }
        }
    },

    /**
     * Display the admin view.
     */
    admin: function() {
        if (!window.server.isPublic() && this.user.get("admin"))
            this.adminView = new AdminView({remove: _.bind(this.onAdminRemove, this)}).render();
    },

    /**
     * Change the content of the admin view to the new user view.
     */
    adminNewUser: function() {
        if (!window.server.isPublic() && this.user.get("admin")) {
            if (_.isNull(this.adminView) || _.isUndefined(this.adminView))
                this.admin();

            this.adminView.setContent(new UserView().render());
        }
    },

    /**
     * Change the content of the admin view to the edit user view.
     *
     * @param id The id of the user to edit
     */
    adminEditUser: function(id) {
        if (!window.server.isPublic() && this.user.get("admin")) {
            if (_.isNull(this.adminView) || _.isUndefined(this.adminView))
                this.admin();

            // Use sessionStorage if available
            if (Modernizr.sessionstorage && !_.isNull(sessionStorage.getItem('edit-user'))) {
                var user = new User();
                user.set(JSON.parse(sessionStorage.getItem('edit-user')));
                this.adminView.setContent(new UserView({model: user}).render());
                sessionStorage.removeItem('edit-user');
                return;
            }

            this.loadingView = new LoadingView($._('Loading user information')).render();

            var that = this;
            var model = new User().load(id, function () {
                that.adminView.setContent(new UserView({model: this}).render());
                that.loadingView.remove();
            });
        }
    },

    /**
     * Display the billing view.
     */
    billing: function(id) {
        if (!window.server.isPublic())
            this.billingView = new BillingView({remove: _.bind(this.onBillingRemove, this)}).render(id);
    },

    /**
     * Display an old request by loading it from the server.
     *
     * @param id The id of the old request
     */
    request: function(id) {
        if (_.isNaN(id) || id <= 0)
            return;

        window.api.getResponse({
            id: id,
            callback: function(text, success) {
                this.loadingView = new LoadingView($._('Loading route information')).render();
                if (success) {
                    window.map.resetRoute();
                    window.map.resetMarkers();
                    window.map.drawMarkersAndRoute(text);
                }
                this.loadingView.remove();
            }
        });
    },

    /**
     * tries to find a spot.
     *
     * @param q search string
     */
    locate: function(q){
        if (!window.nomApi)
            window.nomApi = new Nominatim();

        window.nomApi.search(q, function (success, data) {
            var spot = null;
            for (var i in data) {
                if (data[i]["class"] == "boundary")
                    continue;
                spot = data[i];
                break;
            }

            if (_.isNull(spot)) {
                new MessageView({title: $._("Error"), message: $._("Couldn't find the spot")}).render();
                return;
            }

            window.map.setCenter({lon: spot.lon, lat: spot.lat}, spot.boundingbox);
        });
    },

    onAdminRemove: function () {
        this.adminView = null;
    },

    onBillingRemove: function () {
        this.billingView = null;
    },

    /**
     * Navigate to the requested url after a successful login.
     */
    onLogin: function (success) {
        if (success && !_.isUndefined(this.lastURL) && !_.isNull(this.lastURL)) {
            this.navigate(this.lastURL, {trigger: true});
            this.lastURL = null;
        }
    }

});
<|endoftext|>


<commit_before>var Game = function (game) {
};

Game.prototype = {
    init: function () {
        this.context = this;
        this.grid_rows = 20;
        this.grid_columns = 20;
        this.tile_width = this.game.world.width / this.grid_rows;
        this.tile_height = this.game.world.height / this.grid_columns;
        this.tiles = {};
        this.cursorManager = new CursorManager(this.game);
        this.vimEngine = new VimEngine(this.cursorManager);
        this.keyboardHandlerManager = new KeyboardHandlerManager(this.context);
    },
    preload: function () {
        this.game.stage.backgroundColor = '#FFFFFF';
        this.cursorManager.loadAsset();
    },
    create: function () {
        this.tileManager = new TilesManager(this.game, this.grid_rows, this.grid_columns, this.tiles);
        this.createTiles();
        var phaserKeyboard = new PhaserKeyboard(this.game);
        this.keyboardHandlerManager.setKeyboard(phaserKeyboard);
        var normalModeKeyboardHandler = new NormalModeKeyboardHandler(this.vimEngine);
        this.keyboardHandlerManager.setKeyHandlers(normalModeKeyboardHandler);
        this.cursorManager.createSprite(this.tile_width, this.tile_height);
    },
    createTiles: function () {
        this.tileManager.init();
    },
    update: function () {

    }
};
<commit_msg>Extracted initialization methods in to separate private functions.<commit_after>var Game = function (game) {
};

Game.prototype = {
    init: function () {
        this.context = this;
        this.grid_rows = 20;
        this.grid_columns = 20;
        this.tile_width = this.game.world.width / this.grid_rows;
        this.tile_height = this.game.world.height / this.grid_columns;
        this.tiles = {};
        this.cursorManager = new CursorManager(this.game);
        this.vimEngine = new VimEngine(this.cursorManager);
        this.keyboardHandlerManager = new KeyboardHandlerManager(this.context);
    },
    preload: function () {
        this.game.stage.backgroundColor = '#FFFFFF';
        this.cursorManager.loadAsset();
    },
    create: function () {
        this._createTiles();
        this._setupKeyboard();
        this.cursorManager.createSprite(this.tile_width, this.tile_height);
    },
    update: function () {

    },
    _createTiles: function () {
        this.tilesManager = new TilesManager(this.game, this.grid_rows, this.grid_columns, this.tiles);
        this.tilesManager.init();
    },
    _setupKeyboard: function () {
        this.keyboardHandlerManager.setKeyboard(new PhaserKeyboard(this.game));
        this.keyboardHandlerManager.setKeyHandlers(new NormalModeKeyboardHandler(this.vimEngine));
    }
};
<|endoftext|>


<commit_before>/*!
 * Copyright (c) 2012, Joyent, Inc. All rights reserved.
 *
 * HTTP endpoints validation routines.
 *
 */

var sprintf = require('sprintf').sprintf;
var verror = require('verror');
var restify = require('restify');
var restify_validator = require('restify-validator');

// Restcode = InvalidParameters

// Types of errors:
// ErrorInvalidType
// ErrorInvalidValue
// Error
function formatValidationErrors(validationErrors) {
    return {
        code: 'InvalidParameters',
        message: 'Request parameters failed validation',
        errors: validationErrors.map(function (e) {
            return {
                field: e.param,
                code: 'Invalid',
                message: e.msg
            };
        })
    };
}

/**
 * We will monkey patch validator objects so that we can validate non-string
 * values as well.
 */

restify_validator.Validator.prototype.assert =
restify_validator.Validator.prototype.validate =
restify_validator.Validator.prototype.check = function (str, fail_msg) {
    this.str = str;
    this.msg = fail_msg;
    this._errors = this._errors || [];
    return this;
};

function ensureParamsValid(req, res, paramRules, opts) {
    var rule;
    var skip = false;

    opts = opts || {};

    if (opts.strict) {
        req.log.info({ params: req.params }, 'params');
        for (var param in req.params) {
            if (!paramRules.hasOwnProperty(param)) {
                var error = new restify.InvalidArgumentError(
                    param + ' is not an explicitly defined parameter');
                res.send(409, error);
                return error;
            }
        }
    }

    for (var paramName in paramRules) {
        var assertion = null;

        for (var ruleIdx in paramRules[paramName]) {
            rule = paramRules[paramName][ruleIdx];
            var ruleName;

            if (global.toString.call(rule) == '[object String]') {
                ruleName = rule;
                rule = [ruleName];
            } else if (Array.isArray(rule)) {
                ruleName = rule[0];
            }

            if (!ruleName) {
                throw new Error('ruleName was not');
            }

            var sanitize;

            switch (ruleName) {
                case 'optional':
                    if (Number(ruleIdx) !== 0) {
                        throw new verror.VError(
                            'Rule \'optional\' first must be'
                            + ' first rule specified ruleIdx (%s)',
                            ruleIdx);
                    }

                    if (!req.params.hasOwnProperty(paramName)) {
                        skip = true;
                        req.params[paramName] = rule[1];
                        break;
                    }
                    break;

                case 'sanitize':
                    if (!sanitize) {
                        sanitize = req.sanitize(paramName);
                    }

                    sanitize[rule[1]]();
                    break;

                default:
                    break;
            }

            if (skip) {
                skip = false;
                break;
            }

            if (!skip && (ruleName === 'optional' || ruleName === 'sanitize')) {
                continue;
            }

            if (!assertion) {
                assertion = req.assert(
                    paramName,
                    sprintf('Invalid value for param \'%s\'', paramName));

                assertion.isObjectType = function () {
                    var type = Object.prototype.toString.call(this.str);
                    if (type !== '[object Object]') {
                        this.error(
                           'value is not an object. (was: ' + type + ')');
                    }
                    return this;
                };

                assertion.isStringType = function () {
                    var type = Object.prototype.toString.call(this.str);
                    if (type !== '[object String]') {
                        this.error('value was not a string');
                    }
                    return this;
                };

                assertion.isArrayType = function () {
                    if (!Array.isArray(this.str)) {
                        this.error('value was not an array');
                    }
                    return this;
                };

                assertion.isNumberType = function () {
                    if (isNaN(this.str)) {
                        this.error('value was not a number');
                    }
                    return this;
                };

                assertion.isNumberOrStringType = function () {
                    if (!isNaN(this.str)) {
                        return this;
                    }
                    var type = Object.prototype.toString.call(this.str);
                    if (type !== '[object String]') {
                        this.error('value was not a number or string');
                    }
                    return this;
                };

                assertion.isTrim = function () {
                    if ((this.str + '').match(/^\s+|\s+$/)) {
                        this.error(
                            'value contains leading or trailing whitespace');
                    }
                    return this;
                };

                assertion.isBooleanType = function () {
                    if (this.str !== true && this.str !== false) {
                        this.error('value was not true or false');
                    }
                    return this;
                };

                assertion.isBooleanString = function () {
                    if (!(this.str + '').match(/^(true|false)$/i)) {
                        this.error('value was not \'true\' or \'false\'');
                    }
                    return this;
                };
            }

            if (!assertion[ruleName]) {
                throw new verror.VError('Unknown rule: %s', ruleName);
            }

            assertion[ruleName].apply(assertion, rule.slice(1));
        }
    }
    var errors = req.validationErrors();
    if (errors) {
        errors = formatValidationErrors(errors);
        res.send(
            500,
            errors);
        return errors;
    }

    return null;
}

function updateLimitedToInternalMetadata(req, res, name) {
    if (req.params.hasOwnProperty('update')) {
        var keys = Object.keys(req.params.update);
        if (keys.length != 1 || keys[0] !== 'set_internal_metadata') {
            res.send(
                500,
                new restify.InvalidArgumentError(
                    'Vm.%s update param only allows set_internal_metadata, '
                    + 'received: [%s]', name, keys));
            return true;
       }
    }
    return null;
}

exports.ensureParamsValid = ensureParamsValid;
exports.formatValidationErrors = formatValidationErrors;
exports.assert = {
    updateLimitedToInternalMetadata: updateLimitedToInternalMetadata
};
<commit_msg>CNAPI-633: CNAPI should raise an error when endpoint parameters not explicitly defined are passed in (drop debugging output)<commit_after>/*!
 * Copyright (c) 2012, Joyent, Inc. All rights reserved.
 *
 * HTTP endpoints validation routines.
 *
 */

var sprintf = require('sprintf').sprintf;
var verror = require('verror');
var restify = require('restify');
var restify_validator = require('restify-validator');

// Restcode = InvalidParameters

// Types of errors:
// ErrorInvalidType
// ErrorInvalidValue
// Error
function formatValidationErrors(validationErrors) {
    return {
        code: 'InvalidParameters',
        message: 'Request parameters failed validation',
        errors: validationErrors.map(function (e) {
            return {
                field: e.param,
                code: 'Invalid',
                message: e.msg
            };
        })
    };
}

/**
 * We will monkey patch validator objects so that we can validate non-string
 * values as well.
 */

restify_validator.Validator.prototype.assert =
restify_validator.Validator.prototype.validate =
restify_validator.Validator.prototype.check = function (str, fail_msg) {
    this.str = str;
    this.msg = fail_msg;
    this._errors = this._errors || [];
    return this;
};

function ensureParamsValid(req, res, paramRules, opts) {
    var rule;
    var skip = false;

    opts = opts || {};

    if (opts.strict) {
        for (var param in req.params) {
            if (!paramRules.hasOwnProperty(param)) {
                var error = new restify.InvalidArgumentError(
                    param + ' is not an explicitly defined parameter');
                res.send(409, error);
                return error;
            }
        }
    }

    for (var paramName in paramRules) {
        var assertion = null;

        for (var ruleIdx in paramRules[paramName]) {
            rule = paramRules[paramName][ruleIdx];
            var ruleName;

            if (global.toString.call(rule) == '[object String]') {
                ruleName = rule;
                rule = [ruleName];
            } else if (Array.isArray(rule)) {
                ruleName = rule[0];
            }

            if (!ruleName) {
                throw new Error('ruleName was not');
            }

            var sanitize;

            switch (ruleName) {
                case 'optional':
                    if (Number(ruleIdx) !== 0) {
                        throw new verror.VError(
                            'Rule \'optional\' first must be'
                            + ' first rule specified ruleIdx (%s)',
                            ruleIdx);
                    }

                    if (!req.params.hasOwnProperty(paramName)) {
                        skip = true;
                        req.params[paramName] = rule[1];
                        break;
                    }
                    break;

                case 'sanitize':
                    if (!sanitize) {
                        sanitize = req.sanitize(paramName);
                    }

                    sanitize[rule[1]]();
                    break;

                default:
                    break;
            }

            if (skip) {
                skip = false;
                break;
            }

            if (!skip && (ruleName === 'optional' || ruleName === 'sanitize')) {
                continue;
            }

            if (!assertion) {
                assertion = req.assert(
                    paramName,
                    sprintf('Invalid value for param \'%s\'', paramName));

                assertion.isObjectType = function () {
                    var type = Object.prototype.toString.call(this.str);
                    if (type !== '[object Object]') {
                        this.error(
                           'value is not an object. (was: ' + type + ')');
                    }
                    return this;
                };

                assertion.isStringType = function () {
                    var type = Object.prototype.toString.call(this.str);
                    if (type !== '[object String]') {
                        this.error('value was not a string');
                    }
                    return this;
                };

                assertion.isArrayType = function () {
                    if (!Array.isArray(this.str)) {
                        this.error('value was not an array');
                    }
                    return this;
                };

                assertion.isNumberType = function () {
                    if (isNaN(this.str)) {
                        this.error('value was not a number');
                    }
                    return this;
                };

                assertion.isNumberOrStringType = function () {
                    if (!isNaN(this.str)) {
                        return this;
                    }
                    var type = Object.prototype.toString.call(this.str);
                    if (type !== '[object String]') {
                        this.error('value was not a number or string');
                    }
                    return this;
                };

                assertion.isTrim = function () {
                    if ((this.str + '').match(/^\s+|\s+$/)) {
                        this.error(
                            'value contains leading or trailing whitespace');
                    }
                    return this;
                };

                assertion.isBooleanType = function () {
                    if (this.str !== true && this.str !== false) {
                        this.error('value was not true or false');
                    }
                    return this;
                };

                assertion.isBooleanString = function () {
                    if (!(this.str + '').match(/^(true|false)$/i)) {
                        this.error('value was not \'true\' or \'false\'');
                    }
                    return this;
                };
            }

            if (!assertion[ruleName]) {
                throw new verror.VError('Unknown rule: %s', ruleName);
            }

            assertion[ruleName].apply(assertion, rule.slice(1));
        }
    }
    var errors = req.validationErrors();
    if (errors) {
        errors = formatValidationErrors(errors);
        res.send(
            500,
            errors);
        return errors;
    }

    return null;
}

function updateLimitedToInternalMetadata(req, res, name) {
    if (req.params.hasOwnProperty('update')) {
        var keys = Object.keys(req.params.update);
        if (keys.length != 1 || keys[0] !== 'set_internal_metadata') {
            res.send(
                500,
                new restify.InvalidArgumentError(
                    'Vm.%s update param only allows set_internal_metadata, '
                    + 'received: [%s]', name, keys));
            return true;
       }
    }
    return null;
}

exports.ensureParamsValid = ensureParamsValid;
exports.formatValidationErrors = formatValidationErrors;
exports.assert = {
    updateLimitedToInternalMetadata: updateLimitedToInternalMetadata
};
<|endoftext|>


<commit_before>var fetchResultsStyle = {
    color: '#888',
    textAlign: 'center',
    fontSize: '1.2em'
  };

var SearchActive = React.createClass({
  render: function() {
    return (

        <BusyMessage message="Looking up the Dewey Decimals" />

    );
  }
});

var searchNoResultsStyle = {
  textAlign: 'center'
}, searchNoResultsMessageStyle = {
  fontStyle: 'italic',
  marginRight: '5px'
};

var SearchNoResults = React.createClass({
  render: function() {
    return (

        No one has checked anything in for {this.props.query} yet.
        <Link label="Be the first" href="?publish" />

    );
  }
});

var SearchResults = React.createClass({
  render: function() {
    var rows = [];
    this.props.results.forEach(function(result) {
      console.log(result);
      rows.push(
        <SearchResultRow key={result.name} name={result.name} title={result.value.title} imgUrl={result.value.thumbnail}
                         description={result.value.description} cost={result.cost} nsfw={result.value.nsfw} />
      );
    });
    return (

{rows}

    );
  }
});

var
  searchRowStyle = {
    height: (24 * 7) + 'px',
    overflowY: 'hidden'
  },
  searchRowImgStyle = {
    maxWidth: '100%',
    maxHeight: (24 * 7) + 'px',
    display: 'block',
    marginLeft: 'auto',
    marginRight: 'auto'
  },
  searchRowTitleStyle = {
    fontWeight: 'bold'
  },
  searchRowCostStyle = {
    float: 'right',
  },
  searchRowDescriptionStyle = {
    color : '#444',
    marginTop: '12px',
    fontSize: '0.9em'
  };

var SearchResultRow = React.createClass({
  getInitialState: function() {
    return {
      downloading: false,
      isHovered: false,
    }
  },
  handleMouseOver: function() {
    this.setState({
      isHovered: true,
    });
  },
  handleMouseOut: function() {
    this.setState({
      isHovered: false,
    });
  },
  render: function() {
    var obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.nsfw;
    return (

              <CreditAmount amount={this.props.cost} isEstimate={!this.props.available}/>

lbry://{this.props.name}

{this.props.title}

              <WatchLink streamName={this.props.name} button="primary" />
              <DownloadLink streamName={this.props.name} button="text" />

{this.props.description}

        {
          !obscureNsfw || !this.state.isHovered ? null :

                This content is Not Safe For Work.
                To view adult content, please change your <Link href="?settings" label="Settings" />.

        }

    );
  }
});

var featuredContentItemContainerStyle = {
  position: 'relative',
};

var FeaturedContentItem = React.createClass({
  resolveSearch: false,

  propTypes: {
    name: React.PropTypes.string,
  },

  getInitialState: function() {
    return {
      metadata: null,
      title: null,
      amount: 0.0,
      overlayShowing: false,
    };
  },

  componentWillUnmount: function() {
    this.resolveSearch = false;
  },

  componentDidMount: function() {
    this.resolveSearch = true;

    lbry.search(this.props.name, function(results) {
      var result = results[0];
      var metadata = result.value;
      if (this.resolveSearch)
      {
        this.setState({
          metadata: metadata,
          amount: result.cost,
          available: result.available,
          title: metadata && metadata.title ? metadata.title : ('lbry://' + this.props.name),
        });
      }
    }.bind(this));
  },

  render: function() {
    if (this.state.metadata === null) {
      // Still waiting for metadata, skip render
      return null;
    }

    return (

      <SearchResultRow name={this.props.name} title={this.state.title} imgUrl={this.state.metadata.thumbnail || '/img/default-thumb.svg'}
                 description={this.state.metadata.description} cost={this.state.amount} nsfw={this.state.metadata.nsfw}
                 available={this.state.available} />

);
  }
});

var featuredContentLegendStyle = {
  fontSize: '12px',
  color: '#aaa',
  verticalAlign: '15%',
};

var FeaturedContent = React.createClass({
  render: function() {
    return (

Featured Content

          <FeaturedContentItem name="what" />
          <FeaturedContentItem name="itsadisaster" />
          <FeaturedContentItem name="coloradobridge" />
          <FeaturedContentItem name="samhyde2070" />
          <FeaturedContentItem name="lendersden" />

Community Content <ToolTipLink style={featuredContentLegendStyle} label="What's this?"
            tooltip='Community Content is a public space where anyone can share content with the rest of the LBRY community. Bid on the names "one," "two," "three," "four" and "five" to put your content here!' />

          <FeaturedContentItem name="one" />
          <FeaturedContentItem name="two" />
          <FeaturedContentItem name="three" />
          <FeaturedContentItem name="four" />
          <FeaturedContentItem name="five" />

    );
  }
});

var DiscoverPage = React.createClass({
  userTypingTimer: null,

  componentDidUpdate: function() {
    if (this.props.query != this.state.query)
    {
      this.handleSearchChanged();
    }
  },

  handleSearchChanged: function() {
    this.setState({
      searching: true,
      query: this.props.query,
    });

    lbry.search(this.props.query, this.searchCallback);
  },

  componentDidMount: function() {
    document.title = "Discover";
    if (this.props.query !== '') {
      // Rendering with a query already typed
      this.handleSearchChanged();
    }
  },

  getInitialState: function() {
    return {
      results: [],
      query: this.props.query,
      searching: this.props.query && this.props.query.length > 0
    };
  },

  searchCallback: function(results) {
    if (this.state.searching) //could have canceled while results were pending, in which case nothing to do
    {
      this.setState({
        results: results,
        searching: false //multiple searches can be out, we're only done if we receive one we actually care about
      });
    }
  },

  render: function() {
    return (

        { this.state.searching ? <SearchActive /> : null }
        { !this.state.searching && this.props.query && this.state.results.length ? <SearchResults results={this.state.results} /> : null }
        { !this.state.searching && this.props.query && !this.state.results.length ? <SearchNoResults query={this.props.query} /> : null }
        { !this.props.query && !this.state.searching ? <FeaturedContent /> : null }

    );
  }
});
<commit_msg>Fixed capitalization on featured content item URI<commit_after>var fetchResultsStyle = {
    color: '#888',
    textAlign: 'center',
    fontSize: '1.2em'
  };

var SearchActive = React.createClass({
  render: function() {
    return (

        <BusyMessage message="Looking up the Dewey Decimals" />

    );
  }
});

var searchNoResultsStyle = {
  textAlign: 'center'
}, searchNoResultsMessageStyle = {
  fontStyle: 'italic',
  marginRight: '5px'
};

var SearchNoResults = React.createClass({
  render: function() {
    return (

        No one has checked anything in for {this.props.query} yet.
        <Link label="Be the first" href="?publish" />

    );
  }
});

var SearchResults = React.createClass({
  render: function() {
    var rows = [];
    this.props.results.forEach(function(result) {
      console.log(result);
      rows.push(
        <SearchResultRow key={result.name} name={result.name} title={result.value.title} imgUrl={result.value.thumbnail}
                         description={result.value.description} cost={result.cost} nsfw={result.value.nsfw} />
      );
    });
    return (

{rows}

    );
  }
});

var
  searchRowStyle = {
    height: (24 * 7) + 'px',
    overflowY: 'hidden'
  },
  searchRowImgStyle = {
    maxWidth: '100%',
    maxHeight: (24 * 7) + 'px',
    display: 'block',
    marginLeft: 'auto',
    marginRight: 'auto'
  },
  searchRowTitleStyle = {
    fontWeight: 'bold'
  },
  searchRowCostStyle = {
    float: 'right',
  },
  searchRowDescriptionStyle = {
    color : '#444',
    marginTop: '12px',
    fontSize: '0.9em'
  };

var SearchResultRow = React.createClass({
  getInitialState: function() {
    return {
      downloading: false,
      isHovered: false,
    }
  },
  handleMouseOver: function() {
    this.setState({
      isHovered: true,
    });
  },
  handleMouseOut: function() {
    this.setState({
      isHovered: false,
    });
  },
  render: function() {
    var obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.nsfw;
    return (

              <CreditAmount amount={this.props.cost} isEstimate={!this.props.available}/>

lbry://{this.props.name}

{this.props.title}

              <WatchLink streamName={this.props.name} button="primary" />
              <DownloadLink streamName={this.props.name} button="text" />

{this.props.description}

        {
          !obscureNsfw || !this.state.isHovered ? null :

                This content is Not Safe For Work.
                To view adult content, please change your <Link href="?settings" label="Settings" />.

        }

    );
  }
});

var featuredContentItemContainerStyle = {
  position: 'relative',
};

var FeaturedContentItem = React.createClass({
  resolveSearch: false,

  propTypes: {
    name: React.PropTypes.string,
  },

  getInitialState: function() {
    return {
      metadata: null,
      title: null,
      amount: 0.0,
      overlayShowing: false,
    };
  },

  componentWillUnmount: function() {
    this.resolveSearch = false;
  },

  componentDidMount: function() {
    this.resolveSearch = true;

    lbry.search(this.props.name, function(results) {
      var result = results[0];
      var metadata = result.value;
      if (this.resolveSearch)
      {
        this.setState({
          metadata: metadata,
          amount: result.cost,
          available: result.available,
          title: metadata && metadata.title ? metadata.title : ('lbry://' + this.props.name),
        });
      }
    }.bind(this));
  },

  render: function() {
    if (this.state.metadata === null) {
      // Still waiting for metadata, skip render
      return null;
    }

    return (

      <SearchResultRow name={this.props.name} title={this.state.title} imgUrl={this.state.metadata.thumbnail || '/img/default-thumb.svg'}
                 description={this.state.metadata.description} cost={this.state.amount} nsfw={this.state.metadata.nsfw}
                 available={this.state.available} />

);
  }
});

var featuredContentLegendStyle = {
  fontSize: '12px',
  color: '#aaa',
  verticalAlign: '15%',
};

var FeaturedContent = React.createClass({
  render: function() {
    return (

Featured Content

          <FeaturedContentItem name="what" />
          <FeaturedContentItem name="itsadisaster" />
          <FeaturedContentItem name="coloradobridge" />
          <FeaturedContentItem name="samhyde2070" />
          <FeaturedContentItem name="LendersDen" />

Community Content <ToolTipLink style={featuredContentLegendStyle} label="What's this?"
            tooltip='Community Content is a public space where anyone can share content with the rest of the LBRY community. Bid on the names "one," "two," "three," "four" and "five" to put your content here!' />

          <FeaturedContentItem name="one" />
          <FeaturedContentItem name="two" />
          <FeaturedContentItem name="three" />
          <FeaturedContentItem name="four" />
          <FeaturedContentItem name="five" />

    );
  }
});

var DiscoverPage = React.createClass({
  userTypingTimer: null,

  componentDidUpdate: function() {
    if (this.props.query != this.state.query)
    {
      this.handleSearchChanged();
    }
  },

  handleSearchChanged: function() {
    this.setState({
      searching: true,
      query: this.props.query,
    });

    lbry.search(this.props.query, this.searchCallback);
  },

  componentDidMount: function() {
    document.title = "Discover";
    if (this.props.query !== '') {
      // Rendering with a query already typed
      this.handleSearchChanged();
    }
  },

  getInitialState: function() {
    return {
      results: [],
      query: this.props.query,
      searching: this.props.query && this.props.query.length > 0
    };
  },

  searchCallback: function(results) {
    if (this.state.searching) //could have canceled while results were pending, in which case nothing to do
    {
      this.setState({
        results: results,
        searching: false //multiple searches can be out, we're only done if we receive one we actually care about
      });
    }
  },

  render: function() {
    return (

        { this.state.searching ? <SearchActive /> : null }
        { !this.state.searching && this.props.query && this.state.results.length ? <SearchResults results={this.state.results} /> : null }
        { !this.state.searching && this.props.query && !this.state.results.length ? <SearchNoResults query={this.props.query} /> : null }
        { !this.props.query && !this.state.searching ? <FeaturedContent /> : null }

    );
  }
});
<|endoftext|>


<commit_before>var Checkit = require( 'checkit' ),
    _ = require( 'lodash' ),
    Promise = require( 'bluebird' ),
    bookshelf = require( 'bookshelf' ).bookshelf,
    DuplicateEntityError = require( '../errors/duplicate-entity' );

var BaseModel = module.exports = bookshelf.Model.extend({
    json: [],

    hidden: [],

    fillable: null,

    guarded: null,

    rules: {},

    uniques: [],

    initialize: function() {
        this.on( 'saving', this.validate, this );
    },

    validate: function( model, attrs, options ) {
        return this.validateUniques().then(function() {
            if ( ! options.force ) {
                this.removeNonFillable();
            }

            if ( 'insert' === options.method ) {
                return Checkit( this.rules ).run( this.attributes );
            }
        }.bind( this ) );
    },

    validateUniques: function() {
        var changedUniques = this.uniques.filter(function( unique ) {
            return this.hasChanged( unique );
        }.bind( this ) );

        if ( 0 === changedUniques.length ) {
            return Promise.resolve();
        }

        return new this.constructor().query(function( qb ) {
            for ( var u = 0, ul = changedUniques.length; u < ul; u++ ) {
                var unique = changedUniques[ u ],
                    comparator = ( 0 === u ) ? 'where' : 'orWhere';

                qb[ comparator ]( unique, '=', this.attributes[ unique ] );
            }
        }.bind( this ) ).fetch().then(function( model ) {
            if ( null !== model ) {
                return Promise.reject( new DuplicateEntityError() );
            }
        });
    },

    parse: function( attrs ) {
        attrs = this.parseJson( attrs );
        return attrs;
    },

    format: function( attrs ) {
        attrs = this.formatJson( attrs );
        return attrs;
    },

    parseJson: function( attrs ) {
        this.json.forEach(function( attr ) {
            if ( 'string' === typeof attrs[ attr ] ) {
                attrs[ attr ] = JSON.parse( attrs[ attr ] );
            }
        });

        return attrs;
    },

    formatJson: function( attrs ) {
        this.json.forEach(function( attr ) {
            if ( 'object' === typeof attrs[ attr ] ) {
                attrs[ attr ] = JSON.stringify( attrs[ attr ] );
            }
        });

        return attrs;
    },

    removeNonFillable: function( attrs ) {
        if ( this.fillable instanceof Array ) {
            this.attributes = _.pick( this.attributes, this.fillable.concat( this.hasTimestamps || [] ) );
        }

        if ( this.guarded instanceof Array ) {
            this.attributes = _.omit( this.attributes, this.guarded );
        }
    }
});<commit_msg>Always run validation rules on model<commit_after>var Checkit = require( 'checkit' ),
    _ = require( 'lodash' ),
    Promise = require( 'bluebird' ),
    bookshelf = require( 'bookshelf' ).bookshelf,
    DuplicateEntityError = require( '../errors/duplicate-entity' );

var BaseModel = module.exports = bookshelf.Model.extend({
    json: [],

    hidden: [],

    fillable: null,

    guarded: null,

    rules: {},

    uniques: [],

    initialize: function() {
        this.on( 'saving', this.validate, this );
    },

    validate: function( model, attrs, options ) {
        return this.validateUniques().then(function() {
            if ( ! options.force ) {
                this.removeNonFillable();
            }

            return Checkit( this.rules ).run( this.attributes );
        }.bind( this ) );
    },

    validateUniques: function() {
        var changedUniques = this.uniques.filter(function( unique ) {
            return this.hasChanged( unique );
        }.bind( this ) );

        if ( 0 === changedUniques.length ) {
            return Promise.resolve();
        }

        return new this.constructor().query(function( qb ) {
            for ( var u = 0, ul = changedUniques.length; u < ul; u++ ) {
                var unique = changedUniques[ u ],
                    comparator = ( 0 === u ) ? 'where' : 'orWhere';

                qb[ comparator ]( unique, '=', this.attributes[ unique ] );
            }
        }.bind( this ) ).fetch().then(function( model ) {
            if ( null !== model ) {
                return Promise.reject( new DuplicateEntityError() );
            }
        });
    },

    parse: function( attrs ) {
        attrs = this.parseJson( attrs );
        return attrs;
    },

    format: function( attrs ) {
        attrs = this.formatJson( attrs );
        return attrs;
    },

    parseJson: function( attrs ) {
        this.json.forEach(function( attr ) {
            if ( 'string' === typeof attrs[ attr ] ) {
                attrs[ attr ] = JSON.parse( attrs[ attr ] );
            }
        });

        return attrs;
    },

    formatJson: function( attrs ) {
        this.json.forEach(function( attr ) {
            if ( 'object' === typeof attrs[ attr ] ) {
                attrs[ attr ] = JSON.stringify( attrs[ attr ] );
            }
        });

        return attrs;
    },

    removeNonFillable: function( attrs ) {
        if ( this.fillable instanceof Array ) {
            this.attributes = _.pick( this.attributes, this.fillable.concat( this.hasTimestamps || [] ) );
        }

        if ( this.guarded instanceof Array ) {
            this.attributes = _.omit( this.attributes, this.guarded );
        }
    }
});<|endoftext|>


<commit_before>game.TitleScreen = me.ScreenObject.extend({
	/**
	 *  action to perform on state change
	 */
	onResetEvent: function() {
		me.game.world.addChild(new me.Sprite(0, 0, me.loader.getImage('title-screen')), -10); // TODO

		me.game.world.addChild(new (me.Renderable.extend({
			init: function(){
				this._super(me.Renderable, 'init', [270, 240, 300, 50]);
				this.font = new me.Font("Tahoma", 46, "red");
				me.input.registerPointerEvent('pointerdown', this, this.newGame.bind(this), true);

			},

			draw:  function(renderer){
				this.font.draw(renderer.getContext(), "START A NEW GAME", this.pos.x, this.pos.y);
			},
			update: function(dt){
				return true;
			},

			newGame: function(){
				me.input.releasePointerEvent('pointerdown', this);
				me.save.remove('exp');
		        me.save.remove('exp1');
		        me.save.remove('exp2');
		        me.save.remove('exp3');
		        me.save.remove('exp4');
		        me.state.change(me.state.PLAY);

			}

		 })));

		me.game.world.addChild(new (me.Renderable.extend({
			init: function(){
				this._super(me.Renderable, 'init', [400, 340, 250, 50]);
				this.font = new me.Font("Tahoma", 46, "red");
				me.input.registerPointerEvent('pointerdown', this, this.newGame.bind(this), true);

			},

			draw:  function(renderer){
				this.font.draw(renderer.getContext(), "CONTINUE", this.pos.x, this.pos.y);
			},
			update: function(dt){
				return true;
			},

			newGame: function(){
				me.input.releasePointerEvent('pointerdown', this);
		        me.state.change(me.state.PLAY);

			}

		 })));

	},

	/**
	 *  action to perform when leaving this screen (state change)
	 */
	onDestroyEvent: function() {

	}
});
// titlejs is where we will have all the code for the start screen
//we want our screen to start at the top left corner
//the -10 sets the screen way in the back
//Pointer down is just waiting for the mouse to be clicked down

//True tells you to use the screen coordinates

//we use save.remove becuase we do not want any more save variables

<commit_msg>Video 40 commented<commit_after>game.TitleScreen = me.ScreenObject.extend({
	/**
	 *  action to perform on state change
	 */
	onResetEvent: function() {
		me.game.world.addChild(new me.Sprite(0, 0, me.loader.getImage('title-screen')), -10); // TODO

		me.game.world.addChild(new (me.Renderable.extend({
			init: function(){
				this._super(me.Renderable, 'init', [270, 240, 300, 50]);
				this.font = new me.Font("Tahoma", 46, "red");
				me.input.registerPointerEvent('pointerdown', this, this.newGame.bind(this), true);

			},

			draw:  function(renderer){
				this.font.draw(renderer.getContext(), "START A NEW GAME", this.pos.x, this.pos.y);
			},
			update: function(dt){
				return true;
			},

			newGame: function(){
				me.input.releasePointerEvent('pointerdown', this);
				me.save.remove('exp');
		        me.save.remove('exp1');
		        me.save.remove('exp2');
		        me.save.remove('exp3');
		        me.save.remove('exp4');
		        me.state.change(me.state.PLAY);

			}

		 })));

		me.game.world.addChild(new (me.Renderable.extend({
			init: function(){
				this._super(me.Renderable, 'init', [400, 340, 250, 50]);
				this.font = new me.Font("Tahoma", 46, "red");
				me.input.registerPointerEvent('pointerdown', this, this.newGame.bind(this), true);

			},

			draw:  function(renderer){
				this.font.draw(renderer.getContext(), "CONTINUE", this.pos.x, this.pos.y);
			},
			update: function(dt){
				return true;
			},

			newGame: function(){
				me.input.releasePointerEvent('pointerdown', this);
		        me.state.change(me.state.PLAY);

			}

		 })));

	},

	/**
	 *  action to perform when leaving this screen (state change)
	 */
	onDestroyEvent: function() {

	}
});
// titlejs is where we will have all the code for the start screen
//we want our screen to start at the top left corner
//the -10 sets the screen way in the back
//Pointer down is just waiting for the mouse to be clicked down

//True tells you to use the screen coordinates

//we use save.remove becuase we do not want any more save variables

// video 40

// In continue we dont want to remove any experience we just want to change it state

<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');
const randomString = require('randomstring');
const electron = require('electron');
const childProcess = require('child_process');
const app = electron.app;
const path = require('path');
const isDevEnv = require('../utils/misc.js').isDevEnv;
const isMac = require('../utils/misc.js').isMac;

// Search library
const libSymphonySearch = require('./searchLibrary');

// Crypto Library
const Crypto = require('../cryptoLib');

// Path for the exec file and the user data folder
const userData = path.join(app.getPath('userData'));
const execPath = path.dirname(app.getPath('exe'));

// Constants paths for temp indexing folders
const TEMP_BATCH_INDEX_FOLDER = isDevEnv ? './data/temp_batch_indexes' : path.join(userData, 'data/temp_batch_indexes');
const TEMP_REAL_TIME_INDEX = isDevEnv ? './data/temp_realtime_index' : path.join(userData, 'data/temp_realtime_index');
// Main User Index path
const INDEX_PREFIX = isDevEnv ? './data/search_index' : path.join(userData, 'data/search_index');
// Folder contains real time, batch and user index
const INDEX_DATA_FOLDER = isDevEnv ? './data' : path.join(userData, 'data');
//3 Months
const SEARCH_PERIOD_SUBTRACTOR = 3 * 31 * 24 * 60 * 60 * 1000;
const MINIMUM_DATE = '0000000000000';
const MAXIMUM_DATE = '9999999999999';
const INDEX_VERSION = 'v1';

const SORT_BY_SCORE = 0;
const BATCH_RANDOM_INDEX_PATH_LENGTH = 20;

// library path contractor
const winArchPath = process.arch === 'ia32' ? 'library/indexvalidator-x86.exe' : 'library/indexvalidator-x64.exe';
const rootPath = isMac ? 'library/indexvalidator.exec' : winArchPath;
const productionPath = path.join(execPath, isMac ? '..' : '', rootPath);
const devPath = path.join(__dirname, '..', '..', rootPath);
const INDEX_VALIDATOR = isDevEnv ? devPath : productionPath;

/**
 * This search class communicates with the SymphonySearchEngine C library via node-ffi.
 * There should be only 1 instance of this class in the Electron
 */
class Search {

    /**
     * Constructor for the SymphonySearchEngine library
     * @param userId (for the index folder name)
     * @param key
     */
    constructor(userId, key) {
        this.isInitialized = false;
        this.userId = userId;
        this.key = key;
        this.indexFolderName = INDEX_PREFIX + '_' + this.userId + '_' + INDEX_VERSION;
        this.dataFolder = INDEX_DATA_FOLDER;
        this.realTimeIndex = TEMP_REAL_TIME_INDEX;
        this.batchIndex = TEMP_BATCH_INDEX_FOLDER;
        this.messageData = [];
        this.crypto = new Crypto(userId, key);
        this.decryptAndInit();
    }

    decryptAndInit() {
        this.crypto.decryption().then(() => {
            this.init();
        }).catch(() => {
            this.init();
        });
    }

    /**
     * returns isInitialized boolean
     * @returns {boolean}
     */
    isLibInit() {
        return this.isInitialized;
    }

    /**
     * This init function
     * initialise the SymphonySearchEngine library
     * and creates a folder in the userData
     */
    init() {
        libSymphonySearch.symSEInit();
        libSymphonySearch.symSEEnsureFolderExists(this.dataFolder);
        libSymphonySearch.symSERemoveFolder(this.realTimeIndex);
        libSymphonySearch.symSERemoveFolder(this.batchIndex);
        Search.indexValidator(this.indexFolderName);
        Search.indexValidator(this.realTimeIndex);
        let indexDateStartFrom = new Date().getTime() - SEARCH_PERIOD_SUBTRACTOR;
        // Deleting all the messages except 3 Months from now
        libSymphonySearch.symSEDeleteMessages(this.indexFolderName, null,
            MINIMUM_DATE, indexDateStartFrom.toString());
        this.isInitialized = true;
    }

    /**
     * An array of messages is passed for indexing
     * it will be indexed in a temporary index folder
     * @param {Array} messages
     * @returns {Promise}
     */
    indexBatch(messages) {
        return new Promise((resolve, reject) => {
            if (!messages) {
                reject(new Error('Messages is required'));
                return;
            }

            if (!(JSON.parse(messages) instanceof Array)) {
                reject(new Error('Messages must be an array'));
                return;
            }

            if (!this.isInitialized) {
                reject(new Error('Library not initialized'));
                return;
            }

            const indexId = randomString.generate(BATCH_RANDOM_INDEX_PATH_LENGTH);
            libSymphonySearch.symSECreatePartialIndexAsync(this.batchIndex, indexId, messages, (err, res) => {
                if (err) {
                    reject(new Error(err));
                }
                resolve(res);
            });
        });
    }

    /**
     * Merging the temporary
     * created from indexBatch()
     */
    mergeIndexBatches() {
        return new Promise((resolve, reject) => {
            libSymphonySearch.symSEMergePartialIndexAsync(this.indexFolderName, this.batchIndex, (err, res) => {
                if (err) {
                    reject(new Error(err));
                }
                libSymphonySearch.symSERemoveFolder(this.batchIndex);
                resolve(res);
            });
        });
    }

    /**
     * An array of messages to be indexed
     * in real time
     * @param message
     */
    realTimeIndexing(message) {
        if (!message) {
            return new Error('Message is required');
        }

        if (!(JSON.parse(message) instanceof Array)){
            return new Error('Message must be an array');
        }

        if (!this.isInitialized) {
            return new Error('Library not initialized');
        }

        let result = libSymphonySearch.symSEIndexRealTime(this.realTimeIndex, message);
        return result === 0 ? "Successful" : result
    }

    /**
     * Reading a json file
     * for the demo search app only
     * @param {String} batch
     * @returns {Promise}
     */
    readJson(batch) {
        return new Promise((resolve, reject) => {
            let dirPath = path.join(execPath, isMac ? '..' : '', 'msgsjson', batch);
            let messageFolderPath = isDevEnv ? path.join('./msgsjson', batch) : dirPath;
            let files = fs.readdirSync(messageFolderPath);
            this.messageData = [];
            files.forEach((file) => {
                let tempPath = path.join(messageFolderPath, file);
                let data = fs.readFileSync(tempPath, "utf8");
                if (data) {
                    try {
                        this.messageData.push(JSON.parse(data));
                    } catch (err) {
                        reject(new Error(err))
                    }
                } else {
                    reject(new Error('Error reading batch'))
                }
            });
            resolve(this.messageData);
        });
    }

    /**
     * Encrypting the index after the merging the index
     * to the main user index
     */
    encryptIndex() {
        this.crypto.encryption().then(() => {
            return 'Success'
        }).catch((e) => {
            throw new Error(e)
        });
    }

    /**
     * This returns the search results
     * which returns a char *
     * @param {String} query
     * @param {Array} senderIds
     * @param {Array} threadIds
     * @param {String} fileType
     * @param {String} startDate
     * @param {String} endDate
     * @param {Number} limit
     * @param {Number} offset
     * @param {Number} sortOrder
     * @returns {Promise}
     */
    searchQuery(query, senderIds, threadIds, fileType, startDate,
                endDate, limit, offset, sortOrder) {

        let _limit = limit;
        let _offset = offset;
        let _sortOrder = sortOrder;

        return new Promise((resolve, reject) => {
            if (!this.isInitialized) {
                reject(new Error('Library not initialized'));
                return;
            }

            if (!fs.existsSync(this.indexFolderName) || !fs.existsSync(this.realTimeIndex)) {
                reject('Index folder does not exist.');
                return;
            }

            let q = Search.constructQuery(query, senderIds, threadIds, fileType);

            if (q === undefined) {
                reject(new Error('Search query error'));
                return;
            }

            let sd = new Date().getTime() - SEARCH_PERIOD_SUBTRACTOR;
            let sd_time = MINIMUM_DATE;
            if (startDate && startDate !== "" && typeof startDate === 'object') {
                sd_time = new Date(startDate).getTime();
                if (sd_time >= sd) {
                    sd_time = sd;
                }
            }

            let ed_time = MAXIMUM_DATE;
            if (endDate && endDate !== "" && typeof endDate === 'object') {
                ed_time = new Date(endDate).getTime();
            }

            if (!_limit && _limit === "" && typeof _limit !== 'number' && Math.round(_limit) !== _limit) {
                _limit = 25;
            }

            if (!_offset && _offset === "" && typeof _offset !== 'number' && Math.round(_offset) !== _offset) {
                _offset = 0
            }

            if (!_sortOrder && _sortOrder === "" && typeof _sortOrder !== 'number' && Math.round(_sortOrder) !== _sortOrder) {
                _sortOrder = SORT_BY_SCORE;
            }

            const returnedResult = libSymphonySearch.symSESearch(this.indexFolderName, this.realTimeIndex, q, sd_time.toString(), ed_time.toString(), _offset, _limit, _sortOrder);
            try {
                let ret = returnedResult.readCString();
                resolve(JSON.parse(ret));
            } finally {
                libSymphonySearch.symSEFreeResult(returnedResult);
            }
        });
    }

    /**
     * returns the latest message timestamp
     * from the indexed data
     * @returns {Promise}
     */
    getLatestMessageTimestamp() {
        return new Promise((resolve, reject) => {
            if (!this.isInitialized) {
                reject('Not initialized');
                return;
            }

            if (!fs.existsSync(this.indexFolderName)) {
                reject('Index folder does not exist.');
                return;
            }

            libSymphonySearch.symSEGetLastMessageTimestampAsync(this.indexFolderName, (err, res) => {
                if (err) {
                    reject(new Error(err));
                }
                const returnedResult = res;
                try {
                    let ret = returnedResult.readCString();
                    resolve(ret);
                } finally {
                    libSymphonySearch.symSEFreeResult(returnedResult);
                }
            });
        });
    }

    /**
     * This the query constructor
     * for the search function
     * @param {String} searchQuery
     * @param {Array} senderId
     * @param {Array} threadId
     * @param fileType
     * @returns {string}
     */
    static constructQuery(searchQuery, senderId, threadId, fileType) {

        let query = "";
        if(searchQuery !== undefined) {
            query = searchQuery.trim().toLowerCase(); //to prevent injection of AND and ORs
        }
        let q = "";
        let hashTags = Search.getHashTags(query);
        let hashCashTagQuery = "";

        if(hashTags.length > 0) {
            hashCashTagQuery = " OR tags:(";
            hashTags.forEach((item) => {
                hashCashTagQuery = hashCashTagQuery + "\"" + item + "\" "
            });
            hashCashTagQuery += ")";
        }

        let hasAttachments = false;
        let additionalAttachmentQuery = "";
        if(fileType) {
            hasAttachments = true;
            if(fileType === "attachment") {
                additionalAttachmentQuery = "(hasfiles:true)";
            } else {
                additionalAttachmentQuery = "(filetype:(" + fileType +"))";
            }
        }

        if (query.length > 0 ) {
            q = "((text:(" + query + "))" + hashCashTagQuery ;
            if(hasAttachments) {
                q += " OR (filename:(" + query + "))" ;
            }
            q = q + ")";
        }

        q = Search.appendFilterQuery(q, "senderId", senderId);
        q = Search.appendFilterQuery(q, "threadId", threadId);

        if(q === "") {
            if(hasAttachments) {
                q = additionalAttachmentQuery;
            } else {
                q = undefined; //will be handled in the search function
            }
        } else {
            if(hasAttachments){
                q = q + " AND " + additionalAttachmentQuery
            }
        }
        return q;
    }

    /**
     * appending the senderId and threadId for the query
     * @param {String} query
     * @param {String} fieldName
     * @param {Array} valueArray
     * @returns {string}
     */
    static appendFilterQuery(query, fieldName, valueArray) {
        let q = "";
        if (valueArray && valueArray.length > 0 ) {

            q += "(" + fieldName +":(";
            valueArray.forEach((item)=>{
                q+= "\"" + item + "\" ";
            });
            q += "))";
            if(query.length > 0 ) {
                q = query + " AND " + q;
            }

        } else {
            q = query;
        }

        return q;
    }

    // hashtags can have any characters(before the latest release it was
    // not like this). So the only regex is splitting the search query based on
    // whitespaces
    /**
     * return the hash cash
     * tags from the query
     * @param {String} query
     * @returns {Array}
     */
    static getHashTags(query) {
        let hashTags = [];
        let tokens = query.toLowerCase()
            .trim()
            .replace(/\s\s+/g, ' ')
            .split(' ').filter((el) => {return el.length !== 0});
        tokens.forEach((item) => {
            if (item.startsWith('#') || item.startsWith('$')) {
                hashTags.push(item);
            }
        });
        return hashTags;
    }

    /**
     * Validate the index folder exist or not
     * @param {String} file
     * @returns {*}
     */
    static indexValidator(file) {
        let data;
        let result = childProcess.execFileSync(INDEX_VALIDATOR, [file]).toString();
        try {
            data = JSON.parse(result);
            if (data.status === 'OK') {
                return data;
            }
            return new Error('Unable validate index folder')
        } catch (err) {
            throw new Error(err);
        }
    }
}

/**
 * Exporting the search library
 * @type {{Search: Search}}
 */
module.exports = {
    Search: Search
};
<commit_msg>SEARCH-273<commit_after>'use strict';

const fs = require('fs');
const randomString = require('randomstring');
const electron = require('electron');
const childProcess = require('child_process');
const app = electron.app;
const path = require('path');
const isDevEnv = require('../utils/misc.js').isDevEnv;
const isMac = require('../utils/misc.js').isMac;

// Search library
const libSymphonySearch = require('./searchLibrary');

// Crypto Library
const Crypto = require('../cryptoLib');

// Path for the exec file and the user data folder
const userData = path.join(app.getPath('userData'));
const execPath = path.dirname(app.getPath('exe'));

// Constants paths for temp indexing folders
const TEMP_BATCH_INDEX_FOLDER = isDevEnv ? './data/temp_batch_indexes' : path.join(userData, 'data/temp_batch_indexes');
const TEMP_REAL_TIME_INDEX = isDevEnv ? './data/temp_realtime_index' : path.join(userData, 'data/temp_realtime_index');
// Main User Index path
const INDEX_PREFIX = isDevEnv ? './data/search_index' : path.join(userData, 'data/search_index');
// Folder contains real time, batch and user index
const INDEX_DATA_FOLDER = isDevEnv ? './data' : path.join(userData, 'data');
//3 Months
const SEARCH_PERIOD_SUBTRACTOR = 3 * 31 * 24 * 60 * 60 * 1000;
const MINIMUM_DATE = '0000000000000';
const MAXIMUM_DATE = '9999999999999';
const INDEX_VERSION = 'v1';

const SORT_BY_SCORE = 0;
const BATCH_RANDOM_INDEX_PATH_LENGTH = 20;

// library path contractor
const winArchPath = process.arch === 'ia32' ? 'library/indexvalidator-x86.exe' : 'library/indexvalidator-x64.exe';
const rootPath = isMac ? 'library/indexvalidator.exec' : winArchPath;
const productionPath = path.join(execPath, isMac ? '..' : '', rootPath);
const devPath = path.join(__dirname, '..', '..', rootPath);
const INDEX_VALIDATOR = isDevEnv ? devPath : productionPath;

/**
 * This search class communicates with the SymphonySearchEngine C library via node-ffi.
 * There should be only 1 instance of this class in the Electron
 */
class Search {

    /**
     * Constructor for the SymphonySearchEngine library
     * @param userId (for the index folder name)
     * @param key
     */
    constructor(userId, key) {
        this.isInitialized = false;
        this.userId = userId;
        this.key = key;
        this.indexFolderName = INDEX_PREFIX + '_' + this.userId + '_' + INDEX_VERSION;
        this.dataFolder = INDEX_DATA_FOLDER;
        this.realTimeIndex = TEMP_REAL_TIME_INDEX;
        this.batchIndex = TEMP_BATCH_INDEX_FOLDER;
        this.messageData = [];
        this.crypto = new Crypto(userId, key);
        this.decryptAndInit();
    }

    decryptAndInit() {
        this.crypto.decryption().then(() => {
            this.init();
        }).catch(() => {
            this.init();
        });
    }

    /**
     * returns isInitialized boolean
     * @returns {boolean}
     */
    isLibInit() {
        return this.isInitialized;
    }

    /**
     * This init function
     * initialise the SymphonySearchEngine library
     * and creates a folder in the userData
     */
    init() {
        libSymphonySearch.symSEInit();
        libSymphonySearch.symSEEnsureFolderExists(this.dataFolder);
        libSymphonySearch.symSERemoveFolder(this.realTimeIndex);
        libSymphonySearch.symSERemoveFolder(this.batchIndex);
        Search.indexValidator(this.indexFolderName);
        Search.indexValidator(this.realTimeIndex);
        let indexDateStartFrom = new Date().getTime() - SEARCH_PERIOD_SUBTRACTOR;
        // Deleting all the messages except 3 Months from now
        libSymphonySearch.symSEDeleteMessages(this.indexFolderName, null,
            MINIMUM_DATE, indexDateStartFrom.toString());
        this.isInitialized = true;
    }

    /**
     * An array of messages is passed for indexing
     * it will be indexed in a temporary index folder
     * @param {Array} messages
     * @returns {Promise}
     */
    indexBatch(messages) {
        return new Promise((resolve, reject) => {
            if (!messages) {
                reject(new Error('Messages is required'));
                return;
            }

            if (!(JSON.parse(messages) instanceof Array)) {
                reject(new Error('Messages must be an array'));
                return;
            }

            if (!this.isInitialized) {
                reject(new Error('Library not initialized'));
                return;
            }

            const indexId = randomString.generate(BATCH_RANDOM_INDEX_PATH_LENGTH);
            libSymphonySearch.symSECreatePartialIndexAsync(this.batchIndex, indexId, messages, (err, res) => {
                if (err) {
                    reject(new Error(err));
                }
                resolve(res);
            });
        });
    }

    /**
     * Merging the temporary
     * created from indexBatch()
     */
    mergeIndexBatches() {
        return new Promise((resolve, reject) => {
            libSymphonySearch.symSEMergePartialIndexAsync(this.indexFolderName, this.batchIndex, (err, res) => {
                if (err) {
                    reject(new Error(err));
                }
                libSymphonySearch.symSERemoveFolder(this.batchIndex);
                resolve(res);
            });
        });
    }

    /**
     * An array of messages to be indexed
     * in real time
     * @param message
     */
    realTimeIndexing(message) {
        if (!message) {
            return new Error('Message is required');
        }

        if (!(JSON.parse(message) instanceof Array)){
            return new Error('Message must be an array');
        }

        if (!this.isInitialized) {
            return new Error('Library not initialized');
        }

        let result = libSymphonySearch.symSEIndexRealTime(this.realTimeIndex, message);
        return result === 0 ? "Successful" : result
    }

    /**
     * Reading a json file
     * for the demo search app only
     * @param {String} batch
     * @returns {Promise}
     */
    readJson(batch) {
        return new Promise((resolve, reject) => {
            let dirPath = path.join(execPath, isMac ? '..' : '', 'msgsjson', batch);
            let messageFolderPath = isDevEnv ? path.join('./msgsjson', batch) : dirPath;
            let files = fs.readdirSync(messageFolderPath);
            this.messageData = [];
            files.forEach((file) => {
                let tempPath = path.join(messageFolderPath, file);
                let data = fs.readFileSync(tempPath, "utf8");
                if (data) {
                    try {
                        this.messageData.push(JSON.parse(data));
                    } catch (err) {
                        reject(new Error(err))
                    }
                } else {
                    reject(new Error('Error reading batch'))
                }
            });
            resolve(this.messageData);
        });
    }

    /**
     * Encrypting the index after the merging the index
     * to the main user index
     */
    encryptIndex() {
        this.crypto.encryption().then(() => {
            return 'Success'
        }).catch((e) => {
            throw new Error(e)
        });
    }

    /**
     * This returns the search results
     * which returns a char *
     * @param {String} query
     * @param {Array} senderIds
     * @param {Array} threadIds
     * @param {String} fileType
     * @param {String} startDate
     * @param {String} endDate
     * @param {Number} limit
     * @param {Number} offset
     * @param {Number} sortOrder
     * @returns {Promise}
     */
    searchQuery(query, senderIds, threadIds, fileType, startDate,
                endDate, limit, offset, sortOrder) {

        let _limit = limit;
        let _offset = offset;
        let _sortOrder = sortOrder;

        return new Promise((resolve, reject) => {
            if (!this.isInitialized) {
                reject(new Error('Library not initialized'));
                return;
            }

            if (!fs.existsSync(this.indexFolderName) || !fs.existsSync(this.realTimeIndex)) {
                reject('Index folder does not exist.');
                return;
            }

            let q = Search.constructQuery(query, senderIds, threadIds, fileType);

            if (q === undefined) {
                reject(new Error('Search query error'));
                return;
            }

            let sd = new Date().getTime() - SEARCH_PERIOD_SUBTRACTOR;
            let sd_time = MINIMUM_DATE;
            if (startDate && startDate !== "" && typeof startDate === 'object') {
                sd_time = new Date(startDate).getTime();
                if (sd_time >= sd) {
                    sd_time = sd;
                }
            }

            let ed_time = MAXIMUM_DATE;
            if (endDate && endDate !== "" && typeof endDate === 'object') {
                ed_time = new Date(endDate).getTime();
            }

            if (!_limit && _limit === "" && typeof _limit !== 'number' && Math.round(_limit) !== _limit) {
                _limit = 25;
            }

            if (!_offset && _offset === "" && typeof _offset !== 'number' && Math.round(_offset) !== _offset) {
                _offset = 0
            }

            if (!_sortOrder && _sortOrder === "" && typeof _sortOrder !== 'number' && Math.round(_sortOrder) !== _sortOrder) {
                _sortOrder = SORT_BY_SCORE;
            }

            const returnedResult = libSymphonySearch.symSESearch(this.indexFolderName, this.realTimeIndex, q, sd_time.toString(), ed_time.toString(), _offset, _limit, _sortOrder);
            try {
                let ret = returnedResult.readCString();
                resolve(JSON.parse(ret));
            } finally {
                libSymphonySearch.symSEFreeResult(returnedResult);
            }
        });
    }

    /**
     * returns the latest message timestamp
     * from the indexed data
     * @returns {Promise}
     */
    getLatestMessageTimestamp() {
        return new Promise((resolve, reject) => {
            if (!this.isInitialized) {
                reject('Not initialized');
                return;
            }

            if (!fs.existsSync(this.indexFolderName)) {
                reject('Index folder does not exist.');
                return;
            }

            libSymphonySearch.symSEGetLastMessageTimestampAsync(this.indexFolderName, (err, res) => {
                if (err) {
                    reject(new Error(err));
                }
                const returnedResult = res;
                try {
                    let ret = returnedResult.readCString();
                    resolve(ret);
                } finally {
                    libSymphonySearch.symSEFreeResult(returnedResult);
                }
            });
        });
    }

    /**
     * This the query constructor
     * for the search function
     * @param {String} searchQuery
     * @param {Array} senderId
     * @param {Array} threadId
     * @param {String} fileType
     * @returns {string}
     */
    static constructQuery(searchQuery, senderId, threadId, fileType) {

        let query = "";
        if(searchQuery !== undefined) {
            query = searchQuery.trim().toLowerCase(); //to prevent injection of AND and ORs
        }
        let q = "";
        let hashTags = Search.getHashTags(query);
        let hashCashTagQuery = "";

        if(hashTags.length > 0) {
            hashCashTagQuery = " OR tags:(";
            hashTags.forEach((item) => {
                hashCashTagQuery = hashCashTagQuery + "\"" + item + "\" "
            });
            hashCashTagQuery += ")";
        }

        let hasAttachments = false;
        let additionalAttachmentQuery = "";
        if(fileType) {
            hasAttachments = true;
            if(fileType === "attachment") {
                additionalAttachmentQuery = "(hasfiles:true)";
            } else {
                additionalAttachmentQuery = "(filetype:(" + fileType +"))";
            }
        }

        if (query.length > 0 ) {
            q = "((text:(" + query + "))" + hashCashTagQuery ;
            if(hasAttachments) {
                q += " OR (filename:(" + query + "))" ;
            }
            q = q + ")";
        }

        q = Search.appendFilterQuery(q, "senderId", senderId);
        q = Search.appendFilterQuery(q, "threadId", threadId);

        if(q === "") {
            if(hasAttachments) {
                q = additionalAttachmentQuery;
            } else {
                q = undefined; //will be handled in the search function
            }
        } else {
            if(hasAttachments){
                q = q + " AND " + additionalAttachmentQuery
            }
        }
        return q;
    }

    /**
     * appending the senderId and threadId for the query
     * @param {String} query
     * @param {String} fieldName
     * @param {Array} valueArray
     * @returns {string}
     */
    static appendFilterQuery(query, fieldName, valueArray) {
        let q = "";
        if (valueArray && valueArray.length > 0 ) {

            q += "(" + fieldName +":(";
            valueArray.forEach((item)=>{
                q+= "\"" + item + "\" ";
            });
            q += "))";
            if(query.length > 0 ) {
                q = query + " AND " + q;
            }

        } else {
            q = query;
        }

        return q;
    }

    // hashtags can have any characters(before the latest release it was
    // not like this). So the only regex is splitting the search query based on
    // whitespaces
    /**
     * return the hash cash
     * tags from the query
     * @param {String} query
     * @returns {Array}
     */
    static getHashTags(query) {
        let hashTags = [];
        let tokens = query.toLowerCase()
            .trim()
            .replace(/\s\s+/g, ' ')
            .split(' ').filter((el) => {return el.length !== 0});
        tokens.forEach((item) => {
            if (item.startsWith('#') || item.startsWith('$')) {
                hashTags.push(item);
            }
        });
        return hashTags;
    }

    /**
     * Validate the index folder exist or not
     * @param {String} file
     * @returns {*}
     */
    static indexValidator(file) {
        let data;
        let result = childProcess.execFileSync(INDEX_VALIDATOR, [file]).toString();
        try {
            data = JSON.parse(result);
            if (data.status === 'OK') {
                return data;
            }
            return new Error('Unable validate index folder')
        } catch (err) {
            throw new Error(err);
        }
    }
}

/**
 * Exporting the search library
 * @type {{Search: Search}}
 */
module.exports = {
    Search: Search
};
<|endoftext|>