<commit_before>var express    = require('express');
var app        = express();
var bodyParser = require('body-parser');
var request = require('request');
var _ = require('underscore');
var constants = require('bastly_constants');
var program = require('commander');

program
  .version('0.0.1')
  .usage('connector-rest --atahualpa <IP> --orion <IP:port> --callback <IP:port>')
  .option('-a, --atahualpa <IP>', 'specify atahualpa IP to connect to', '127.0.0.1')
  .option('-o, --orion <IP:port>', 'specify orion IP and PORT to connect to', '127.0.0.1:1026')
  .option('-c, --callback <IP:port>', 'specify the callback ip of this endpoint', '127.0.0.1:8080')
  .parse(process.argv);

console.log('Running with atahualpa on: ', program.atahualpa);
console.log('Running with orion on: ', program.orion);
console.log('Running with callback ip on: ', program.callback);

//allows CORS for everywhere
var allowCrossDomain = function(req, res, next) {
    res.header('Access-Control-Allow-Credentials', true);
    res.header('Access-Control-Allow-Origin', req.headers.origin);
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With, Origin, Accept');
    next();
};

app.use(allowCrossDomain);

var IP_ORION = program.orion;
var IP_ATAHUALPA = program.atahualpa;
var IP_CALLBACK = program.callback;
var bastly = require('bastly')({ipAtahualpa:IP_ATAHUALPA});
// var bastly = require('../sdk-node')({ipAtahualpa:IP_ATAHUALPA});

// this will let us get the data from a POST
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

var port = process.env.PORT || 8080;        // set our port
var subscriptions = {};

console.log('Listenint to port: ', port);

// ROUTES FOR OUR API
// =============================================================================
var router = express.Router();              // get an instance of the express Router

// chaski worker request
router.get('/requestChaski', function(req, res) {
    var channel = req.param('channel');
    var chaskiType = req.param('chaskiType');
    console.log('requesting chaski', channel, chaskiType);
    if(!channel){
        res.json({ message: "must specify ?channel="  });
        return;
    }
    if(!chaskiType){
        res.json({ message: "must specify ?chaskiType=" + constants.CHASKI_TYPE_SOCKETIO + '/' + constants.CHASKI_TYPE_ZEROMQ });
        return;
    }
    bastly.getWorker(channel, chaskiType, function(reply){
        console.log('got reply from get worker: ' + reply);
        res.json({ message: reply  });
    });
});

router.post('/subscribtionObjectStructure', function (req, res){ //551c09c9984d23677ebc3cff
    // console.log('object structure watcher');

    _.each(req.body.contextResponses, function (elem) {

        // console.log(elem.contextElement);
        var attrs = [];
        _.each( elem.contextElement.attributes, function (attribute){
            attrs.push(attribute.name);
        });

        if ( subscriptions[elem.contextElement.id]  === undefined || subscriptions[elem.contextElement.id] === null){

            console.log('create subscription');
            request.post({
                url: 'http://' + IP_ORION + '/v1/subscribeContext',
                json: true,
                body: {
                    "entities": [
                    {
                        "type": "BastlyMSG",
                        "isPattern": "false",
                        "id": elem.contextElement.id
                    }
                    ],
                    "attributes": [],
                    "reference": "http://" + IP_CALLBACK + "/api/subscription",
                    "duration": "P1M",
                    "notifyConditions": [
                    {
                        "type": "ONCHANGE",
                        "condValues": attrs
                    }
                    ],
                    "throttling": "PT1S"
                }
            },
            function (error, response, body) {
                console.log('body', body);
                subscriptions[elem.contextElement.id] = {subscriptionId : body.subscribeResponse.subscriptionId, attributes : attrs};
            });

        } else { // only update subscription if any attrs has change cause it generates a notification.
            if (_.difference(attrs, subscriptions[elem.contextElement.id].attributes).length > 0) {
                console.log('arrays differ');
                console.log('update subscription ', subscriptions[elem.contextElement.id].subscriptionId);

                request.post({
                    url: 'http://' + IP_ORION + '/v1/updateContextSubscription',
                    json: true,
                    body: {
                        "subscriptionId": subscriptions[elem.contextElement.id].subscriptionId,
                        "notifyConditions": [
                        {
                            "type": "ONCHANGE",
                            "condValues": attrs
                        }
                        ]
                    }
                },
                function (error, response, body) {
                    // console.log(body);
                    subscriptions[elem.contextElement.id].subscriptionId = body.subscribeResponse.subscriptionId;
                    subscriptions[elem.contextElement.id].attributes = attrs;
                });
            }  else {
                console.log('arrays do not differ', _.difference(attrs, subscriptions[elem.contextElement.id].attributes));
            }
        }

    });

    res.sendStatus(200);
});

router.post('/subscription', function (req, res) {
    var updatedElement = req.body.contextResponses[0].contextElement;
    var channels = [];
    if (_.findLastIndex(updatedElement.attributes, { name: 'channels' }) != -1) {
        channels = updatedElement.attributes[_.findLastIndex(updatedElement.attributes, { name: 'channels' })].value;
    }

    var apiKey = updatedElement.id.substring("BastlyKey:".length, updatedElement.id.indexOf(":userId:"));
    console.log('Object data updated: ', updatedElement);

    _.each(channels, function (channel) {
        //TODO verify apikey, from is ORION?
        bastly.sendMessage(channel, "ORION", apiKey,  updatedElement, function(reply){
            console.log('messasge ack!', reply);
        });
    });

    res.sendStatus(200);
});

// message publisher
router.post('/publishMessage', function(req, res) {
    var data = JSON.parse(req.body.data);
    var from = req.body.from;
    var to = req.body.to;
    var apiKey = req.body.apiKey;
    bastly.sendMessage(to, from, apiKey, data, function(repply){
        console.log('messasge ack!');
        res.json({ message: 'ok' });
    });
});

// REGISTER OUR ROUTES -------------------------------
// all of our routes will be prefixed with /api
app.use('/api', router);

// START THE SERVER
// =============================================================================
app.listen(port);

// Register to all changes in structures in ORION each 30 secs
request.post({
    url: 'http://' + IP_ORION + '/v1/subscribeContext',
    json: true,
    body: {
        "entities": [
            {
                "type": "BastlyMSG",
                "isPattern": "true",
                "id": "BastlyKey:*"
            }
        ],
        "attributes": [],
        "reference": "http://" + IP_CALLBACK + "/api/subscribtionObjectStructure",
        "duration": "P1M",
        "notifyConditions": [
            {
                "type": "ONTIMEINTERVAL",
                "condValues": [
                    "PT30S"
                ]
            }
        ]
    }
},
function (error, response, body) {
    console.log('body 2', body);
});
<commit_msg>more logs from subscription<commit_after>var express    = require('express');
var app        = express();
var bodyParser = require('body-parser');
var request = require('request');
var _ = require('underscore');
var constants = require('bastly_constants');
var program = require('commander');

program
  .version('0.0.1')
  .usage('connector-rest --atahualpa <IP> --orion <IP:port> --callback <IP:port>')
  .option('-a, --atahualpa <IP>', 'specify atahualpa IP to connect to', '127.0.0.1')
  .option('-o, --orion <IP:port>', 'specify orion IP and PORT to connect to', '127.0.0.1:1026')
  .option('-c, --callback <IP:port>', 'specify the callback ip of this endpoint', '127.0.0.1:8080')
  .parse(process.argv);

console.log('Running with atahualpa on: ', program.atahualpa);
console.log('Running with orion on: ', program.orion);
console.log('Running with callback ip on: ', program.callback);

//allows CORS for everywhere
var allowCrossDomain = function(req, res, next) {
    res.header('Access-Control-Allow-Credentials', true);
    res.header('Access-Control-Allow-Origin', req.headers.origin);
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With, Origin, Accept');
    next();
};

app.use(allowCrossDomain);

var IP_ORION = program.orion;
var IP_ATAHUALPA = program.atahualpa;
var IP_CALLBACK = program.callback;
var bastly = require('bastly')({ipAtahualpa:IP_ATAHUALPA});
// var bastly = require('../sdk-node')({ipAtahualpa:IP_ATAHUALPA});

// this will let us get the data from a POST
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

var port = process.env.PORT || 8080;        // set our port
var subscriptions = {};

console.log('Listenint to port: ', port);

// ROUTES FOR OUR API
// =============================================================================
var router = express.Router();              // get an instance of the express Router

// chaski worker request
router.get('/requestChaski', function(req, res) {
    var channel = req.param('channel');
    var chaskiType = req.param('chaskiType');
    console.log('requesting chaski', channel, chaskiType);
    if(!channel){
        res.json({ message: "must specify ?channel="  });
        return;
    }
    if(!chaskiType){
        res.json({ message: "must specify ?chaskiType=" + constants.CHASKI_TYPE_SOCKETIO + '/' + constants.CHASKI_TYPE_ZEROMQ });
        return;
    }
    bastly.getWorker(channel, chaskiType, function(reply){
        console.log('got reply from get worker: ' + reply);
        res.json({ message: reply  });
    });
});

router.post('/subscribtionObjectStructure', function (req, res){ //551c09c9984d23677ebc3cff
    // console.log('object structure watcher');

    _.each(req.body.contextResponses, function (elem) {

        // console.log(elem.contextElement);
        var attrs = [];
        _.each( elem.contextElement.attributes, function (attribute){
            attrs.push(attribute.name);
        });

        if ( subscriptions[elem.contextElement.id]  === undefined || subscriptions[elem.contextElement.id] === null){

            console.log('create subscription');
            request.post({
                url: 'http://' + IP_ORION + '/v1/subscribeContext',
                json: true,
                body: {
                    "entities": [
                    {
                        "type": "BastlyMSG",
                        "isPattern": "false",
                        "id": elem.contextElement.id
                    }
                    ],
                    "attributes": [],
                    "reference": "http://" + IP_CALLBACK + "/api/subscription",
                    "duration": "P1M",
                    "notifyConditions": [
                    {
                        "type": "ONCHANGE",
                        "condValues": attrs
                    }
                    ],
                    "throttling": "PT1S"
                }
            },
            function (error, response, body) {
                console.log('body', body);
                subscriptions[elem.contextElement.id] = {subscriptionId : body.subscribeResponse.subscriptionId, attributes : attrs};
            });

        } else { // only update subscription if any attrs has change cause it generates a notification.
            if (_.difference(attrs, subscriptions[elem.contextElement.id].attributes).length > 0) {
                console.log('arrays differ');
                console.log('update subscription ', subscriptions[elem.contextElement.id].subscriptionId);

                request.post({
                    url: 'http://' + IP_ORION + '/v1/updateContextSubscription',
                    json: true,
                    body: {
                        "subscriptionId": subscriptions[elem.contextElement.id].subscriptionId,
                        "notifyConditions": [
                        {
                            "type": "ONCHANGE",
                            "condValues": attrs
                        }
                        ]
                    }
                },
                function (error, response, body) {
                    // console.log(body);
                    subscriptions[elem.contextElement.id].subscriptionId = body.subscribeResponse.subscriptionId;
                    subscriptions[elem.contextElement.id].attributes = attrs;
                });
            }  else {
                console.log('arrays do not differ', _.difference(attrs, subscriptions[elem.contextElement.id].attributes));
            }
        }

    });

    res.sendStatus(200);
});

router.post('/subscription', function (req, res) {
    var updatedElement = req.body.contextResponses[0].contextElement;
    var channels = [];
    if (_.findLastIndex(updatedElement.attributes, { name: 'channels' }) != -1) {
        channels = updatedElement.attributes[_.findLastIndex(updatedElement.attributes, { name: 'channels' })].value;
    }

    var apiKey = updatedElement.id.substring("BastlyKey:".length, updatedElement.id.indexOf(":userId:"));
    console.log('Object data updated: ', updatedElement);

    _.each(channels, function (channel) {
        //TODO verify apikey, from is ORION?
        bastly.sendMessage(channel, "ORION", apiKey,  updatedElement, function(reply){
            console.log('messasge ack!', reply);
        });
    });

    res.sendStatus(200);
});

// message publisher
router.post('/publishMessage', function(req, res) {
    var data = JSON.parse(req.body.data);
    var from = req.body.from;
    var to = req.body.to;
    var apiKey = req.body.apiKey;
    bastly.sendMessage(to, from, apiKey, data, function(repply){
        console.log('messasge ack!');
        res.json({ message: 'ok' });
    });
});

// REGISTER OUR ROUTES -------------------------------
// all of our routes will be prefixed with /api
app.use('/api', router);

// START THE SERVER
// =============================================================================
app.listen(port);

// Register to all changes in structures in ORION each 30 secs
request.post({
    url: 'http://' + IP_ORION + '/v1/subscribeContext',
    json: true,
    body: {
        "entities": [
            {
                "type": "BastlyMSG",
                "isPattern": "true",
                "id": "BastlyKey:*"
            }
        ],
        "attributes": [],
        "reference": "http://" + IP_CALLBACK + "/api/subscribtionObjectStructure",
        "duration": "P1M",
        "notifyConditions": [
            {
                "type": "ONTIMEINTERVAL",
                "condValues": [
                    "PT30S"
                ]
            }
        ]
    }
},
function (error, response, body) {
    if (err) console.log('err', error);
    console.log('res: ', response);
    console.log('body: ', body);
});
<|endoftext|>


<commit_before>// ==========================================================================
//                          DG.FormulaContext
//
//  Author:   Kirk Swenson
//
//  Copyright (c) 2014 by The Concord Consortium, Inc. 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.
// ==========================================================================

sc_require('formula/formula_common');
sc_require('utilities/object_map');

/** @class DG.FormulaContext

  DG.FormulaContext objects support DG.Formula objects by providing identifier
  binding and evaluation, function binding and evaluation, etc.

  @extends SC.Object
*/
DG.FormulaContext = SC.Object.extend( (function() {

  /**
    Utility function for check argument count.
    @param    {String}    iName -- The name of the function
    @param    {Array}     iArgs -- The arguments to the function
    @param    {Object}    iArgSpecs -- An object which contains argument specs
                            for multiple functions, e.g. iArgsSpecs[iName] is
                            the specification for the 'iName' function. The
                            specification is an object with 'min' and 'max' properties.
   */
  var checkArgs = function( iName, iArgs, iArgSpecs) {
        var fArgs = iArgSpecs && iArgSpecs[iName];
        if( fArgs && (iArgs.length < fArgs.min))
          throw new DG.FuncArgsError( iName, fArgs);
      };

  return {

  /**
    Called when the formula is recompiled to clear any cached data.
    Derived classes may override as appropriate.
   */
  clearCaches: function() {
  },

  /**
    Compiles a variable reference into the JavaScript code for accessing
    the appropriate value. For the base FormulaContext, this means
    binding to global constants such as 'pi' and 'e'.
    @param    {String}    iName -- The variable name to be bound
    @returns  {String}    The JavaScript code for accessing the value
    @throws   {VarReferenceError} Base class throws VarReferenceError for
                                  variable names that are not recognized.
   */
  compileVariable: function( iName) {
    switch( iName) {
    case 'pi':
    case 'π':
      return 'Math.PI';
    case 'e':
      return 'Math.E';
    default:
      var eVars = this.get('eVars');
      if( eVars && (eVars[iName] !== undefined))
        return 'e.' + iName;

      var vars = this.get('vars');
      if( vars && (vars[iName] !== undefined))
        return 'c.vars.' + iName;
    }
    return '(function(){throw new DG.VarReferenceError(' + iName + ');})()';
  },

  /**
    Direct evaluation of the variable without an intervening compilation.
    For the base FormulaContext, this means binding to global constants such as 'pi' and 'e'.
    @param    {String}    iName -- The variable name to be bound
    @returns  {Object}    Return value can be a Number, String, Boolean, error object, etc.
    @throws   {DG.VarReferenceError}  Throws VarReferenceError for variable
                                      names that are not recognized.
   */
  evaluateVariable: function( iName, iEvalContext) {
    switch( iName) {
    case 'pi':
    case 'π':
      return Math.PI;
    case 'e':
      return Math.E;
    default:
      var eValue = iEvalContext && iEvalContext[ iName];
      if( eValue !== undefined)
        return eValue;
      var vars = this.get('vars');
      if( vars && (vars[iName] !== undefined))
        return vars[iName];
    }
    throw new DG.VarReferenceError( iName);
  },

  /**
    Returns true if this context's formula contains aggregate functions, false otherwise.
    @property {Boolean}
   */
  hasAggregates: false,

  /**
    List of function names for use in the Functions popup menu.
    Will need to become hierarchical as the number of functions increases
    beyond the capacity of a simple flat popup menu.
    @property   {Array of String}   Names of functions, e.g. ["abs","acos", ...]
   */
  fnNames: [],

  /**
    Property which provides implementation of functions supported by the FormulaContext.
    @property   {Object}  Map of function name {String} to function implementations {Function}.
   */
  _fns: {

    /**
      Coerces its argument to a boolean value.
      @param    {Object}  The argument to be coerced to boolean
      @returns  {Boolean} The converted boolean value
     */
    'boolean': function(x) {
      return Boolean(x);
    },

    /**
      Returns the fractional part of its argument.
      @param    {Number}  The numeric value whose fractional part is to be returned
      @returns  {Number}  The fractional part of its numeric argument
     */
    'frac': function(x) {
      return x - this.trunc(x);
    },

    /**
      Returns the natural logarithm (base e) of its argument.
      @param    {Number}  The numeric value whose natural log is to be returned
      @returns  {Number}  The natural log of its numeric argument
     */
    'ln': function(x) {
      return Math.log(x);
    },

    /**
      Returns the base 10 logarithm of its argument. Note: We override Math.log
      to provide the base 10 log here. Use ln() for the natural log.
      @param    {Number}  The numeric value base 10 log is to be returned
      @returns  {Number}  The base 10 log of its numeric argument
     */
    'log': function(x) {
      return Math.log(x) / Math.LN10;
    },

    /**
      Coerces its argument to a numeric value.
      @param    {Object}  The argument to be coerced to a number
      @returns  {Number}  The converted numeric value
     */
    'number': function(x) {
      return Number(x);
    },

    /**
      Random number generator. Override of Math.random() to provide more flexibility.
      random()        -- Generates a random number in the range [0,1).
      random(max)     -- Generates a random number in the range [0,max).
      random(min,max) -- Generates a random number in the range [min,max).
      @param    {Number}  x1 -- If present alone, the maximum value of the random number.
                                If first of two argument, the minimum value of the random number.
      @param    {Number}  x2 -- The maximum value of the random number.
      @returns  {Number}  The generated random number
     */
    'random': function(x1,x2) {
      // random()
      if( SC.none(x1))
        return Math.random();
      // random(max)
      if( SC.none(x2))
        return x1 * Math.random();
      // random(min,max)
      return x1 + (x2 - x1) * Math.random();
    },

    /**
      Rounds a number to the nearest integer or specified decimal place.
      @param    {Number}  x -- The number to be rounded
      @param    {Number}  n -- {optional} The number of decimal places to round to (default 0).
      @returns  {Number}  The rounded result
     */
    'round': function(x,n) {
      if( SC.none(n))
        return Math.round(x);
      var npow = Math.pow(10, this.trunc(n));
      return Math.round(npow * x) / npow;
    },

    /**
      Coerces its argument to a string value.
      @param    {Object}  The argument to be coerced to a string
      @returns  {String}  The converted string value
     */
    'string': function(x) {
      return String(x);
    },

    /**
      Returns the integer part of its argument.
      @param    {Number}  The numeric value whose integer part is to be returned
      @returns  {Number}  The integer part of its numeric argument
     */
    'trunc': function(x) {
      return x < 0 ? Math.ceil(x) : Math.floor(x);
    }
  },

  /**
    Property which provides meta-data about the functions supported by the '_fns' property.
    @property   {Object}  Map of name {String} to {Object}.
   */
  _fnsArgs: {
    'boolean': { min:1, max:1 },
    'frac': { min:1, max:1 },
    'ln': { min:1, max:1 },
    'log': { min:1, max:1 },
    'number': { min:1, max:1 },
    'random': { min:0, max:2 },
    'round': { min:1, max:2 },
    'string': { min:1, max:1 },
    'trunc': { min:1, max:1 }
  },

  /**
    Property which provides meta-data about the functions supported by the JavaScript Math class.
    @property   {Object}  Map of name {String} to {Object}.
   */
  _MathArgs: {
    'abs': { min:1, max:1 },
    'acos': { min:1, max:1 },
    'asin': { min:1, max:1 },
    'atan': { min:1, max:1 },
    'atan2': { min:2, max:2 },
    'ceil': { min:1, max:1 },
    'cos': { min:1, max:1 },
    'exp': { min:1, max:1 },
    'floor': { min:1, max:1 },
    //'log': { min:1, max:1 },    // replaced by DG version
    //'max': { min:1, max:'n' },  // replaced by aggregate version
    //'min': { min:1, max:'n' },  // replaced by aggregate version
    'pow': { min:2, max:2 },
    //'random': { min:0, max:0 }, // replaced by DG version
    //'round': { min:1, max:1 },  // replaced by DG version
    'sin': { min:1, max:1 },
    'sqrt': { min:1, max:1 },
    'tan': { min:1, max:1 }
  },

  /**
    Compiles a function reference into the JavaScript code for evaluating
    the appropriate function. For the base FormulaContext, this means
    binding to global functions such as ln(), log(), round(), etc. as well
    as the standard JavaScript Math functions (sin(), cos(), atan()), etc.
    @param    {String}    iName -- The name of the function to be called.
    @param    {Array}     iArgs -- the arguments to the function
    @returns  {String}    The JavaScript code for calling the specified function
    @throws   {DG.FuncReferenceError} Throws DG.FuncReferenceError for function
                                      names that are not recognized.
   */
  compileFunction: function( iName, iArgs) {

    // Functions provided by built-in '_fns' property of context
    var _fns = this.get('_fns');
    if( _fns && typeof _fns[iName] === 'function') {
      checkArgs( iName, iArgs, this.get('_fnsArgs'));
      return 'c._fns.' + iName + '(' + iArgs + ')';
    }

    // Other functions of JavaScript Math object
    if( typeof Math[iName] === 'function') {
      checkArgs( iName, iArgs, this._MathArgs);
      return 'Math.' + iName + '(' + iArgs + ')';
    }

    // Functions provided by client-provided 'fns' property of context
    var fns = this.get('fns');
    if( fns && typeof fns[iName] === 'function') {
      checkArgs( iName, iArgs, this.get('fnsArgs'));
      return 'c.fns.' + iName + '(' + iArgs + ')';
    }

    return '(function(){throw new DG.FuncReferenceError(' + iName + ');})()';
  },

  /**
    Evaluates a function reference directly without compilation. For the base
    FormulaContext, this means binding to global functions such as ln(), log(),
    round(), etc. as well as the standard JavaScript Math functions (sin(), cos(),
    atan(), etc.).
    @param    {String}    iName -- The name of the function to be called.
    @param    {Array}     iArgs -- the arguments to the function
    @returns  {String}    The JavaScript code for calling the specified function
    @throws   {DG.FuncReferenceError} Throws DG.FuncReferenceError for function
                                      names that are not recognized.
   */
  evaluateFunction: function( iName, iArgs) {

    // Functions provided by built-in '_fns' property of context
    var _fns = this.get('_fns');
    if( _fns && typeof _fns[iName] === 'function') {
      checkArgs( iName, iArgs, this.get('_fnsArgs'));
      return _fns[iName].apply( _fns, iArgs);
    }

    // Other functions of JavaScript Math object
    if( typeof Math[iName] === 'function') {
      checkArgs( iName, iArgs, this._MathArgs);
      return Math[iName].apply( Math, iArgs);
    }

    // Functions provided by client-provided 'fns' property of context
    var fns = this.get('fns');
    if( fns && typeof fns[iName] === 'function') {
      checkArgs( iName, iArgs, this.get('fnsArgs'));
      return fns[iName].apply( fns, iArgs);
    }

    throw new DG.FuncReferenceError( iName);
  }

  }; // end of closure return statement

}()));

/**
  Returns an evaluable JavaScript function that returns the value
  of the specified expression. The function created is of the form:
    function( c, e) { return iExpression };
  where c is the formula context/compile context and e is the evaluation context.
  @returns  {Function}  The evaluable function
 */
DG.FormulaContext.createContextFunction = function( iExpression) {
  /* jslint evil:true */  // allow use of the Function constructor
  return new Function('c', 'e', 'return ' + iExpression);
};

/**
  Adds the names of the functions in iModule to the fnNames property,
  for use in creating the function popup menu. The properties of the
  iModule object are assumed to be functions, and the names are taken
  from the names of the properties. This will need some adjustment when
  localization is taken into account.
  @param  {Object}  iModule -- Properties are functions
 */
DG.FormulaContext.registerFnModule = function( iModule) {
  var fnNames = DG.FormulaContext.prototype.fnNames;
  // Use push.apply() to push the array elements instead of the array.
  fnNames.push.apply( fnNames, DG.ObjectMap.keys( iModule));
  fnNames.sort();
};

/**
  Register the base function modules built into the context.
 */
DG.FormulaContext.registerFnModule( DG.FormulaContext.prototype._fns);
DG.FormulaContext.registerFnModule( DG.FormulaContext.prototype._MathArgs);

/**
  Returns an array of function names for all the supported functions,
  along with empty parentheses after the function name.
  @returns  {Array of String}   The array of function names (with parentheses)
 */
DG.FormulaContext.getFunctionNamesWithParentheses = function() {
  var names = DG.FormulaContext.prototype.fnNames;
  return names.map( function( iName) {
                      return iName + "()";
                    });
};

<commit_msg>[Fixed #80229148] Added 'secondsToDate' and 'month' to the list of functions available in the formula editor. Implemented these functions in FormulaContext.<commit_after>// ==========================================================================
//                          DG.FormulaContext
//
//  Author:   Kirk Swenson
//
//  Copyright (c) 2014 by The Concord Consortium, Inc. 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.
// ==========================================================================

sc_require('formula/formula_common');
sc_require('utilities/object_map');

/** @class DG.FormulaContext

  DG.FormulaContext objects support DG.Formula objects by providing identifier
  binding and evaluation, function binding and evaluation, etc.

  @extends SC.Object
*/
DG.FormulaContext = SC.Object.extend( (function() {

  /**
    Utility function for check argument count.
    @param    {String}    iName -- The name of the function
    @param    {Array}     iArgs -- The arguments to the function
    @param    {Object}    iArgSpecs -- An object which contains argument specs
                            for multiple functions, e.g. iArgsSpecs[iName] is
                            the specification for the 'iName' function. The
                            specification is an object with 'min' and 'max' properties.
   */
  var checkArgs = function( iName, iArgs, iArgSpecs) {
        var fArgs = iArgSpecs && iArgSpecs[iName];
        if( fArgs && (iArgs.length < fArgs.min))
          throw new DG.FuncArgsError( iName, fArgs);
      };

  return {

  /**
    Called when the formula is recompiled to clear any cached data.
    Derived classes may override as appropriate.
   */
  clearCaches: function() {
  },

  /**
    Compiles a variable reference into the JavaScript code for accessing
    the appropriate value. For the base FormulaContext, this means
    binding to global constants such as 'pi' and 'e'.
    @param    {String}    iName -- The variable name to be bound
    @returns  {String}    The JavaScript code for accessing the value
    @throws   {VarReferenceError} Base class throws VarReferenceError for
                                  variable names that are not recognized.
   */
  compileVariable: function( iName) {
    switch( iName) {
    case 'pi':
    case 'π':
      return 'Math.PI';
    case 'e':
      return 'Math.E';
    default:
      var eVars = this.get('eVars');
      if( eVars && (eVars[iName] !== undefined))
        return 'e.' + iName;

      var vars = this.get('vars');
      if( vars && (vars[iName] !== undefined))
        return 'c.vars.' + iName;
    }
    return '(function(){throw new DG.VarReferenceError(' + iName + ');})()';
  },

  /**
    Direct evaluation of the variable without an intervening compilation.
    For the base FormulaContext, this means binding to global constants such as 'pi' and 'e'.
    @param    {String}    iName -- The variable name to be bound
    @returns  {Object}    Return value can be a Number, String, Boolean, error object, etc.
    @throws   {DG.VarReferenceError}  Throws VarReferenceError for variable
                                      names that are not recognized.
   */
  evaluateVariable: function( iName, iEvalContext) {
    switch( iName) {
    case 'pi':
    case 'π':
      return Math.PI;
    case 'e':
      return Math.E;
    default:
      var eValue = iEvalContext && iEvalContext[ iName];
      if( eValue !== undefined)
        return eValue;
      var vars = this.get('vars');
      if( vars && (vars[iName] !== undefined))
        return vars[iName];
    }
    throw new DG.VarReferenceError( iName);
  },

  /**
    Returns true if this context's formula contains aggregate functions, false otherwise.
    @property {Boolean}
   */
  hasAggregates: false,

  /**
    List of function names for use in the Functions popup menu.
    Will need to become hierarchical as the number of functions increases
    beyond the capacity of a simple flat popup menu.
    @property   {Array of String}   Names of functions, e.g. ["abs","acos", ...]
   */
  fnNames: [],

  /**
    Property which provides implementation of functions supported by the FormulaContext.
    @property   {Object}  Map of function name {String} to function implementations {Function}.
   */
  _fns: {

    /**
      Coerces its argument to a boolean value.
      @param    {Object}  The argument to be coerced to boolean
      @returns  {Boolean} The converted boolean value
     */
    'boolean': function(x) {
      return Boolean(x);
    },

    /**
      Returns the fractional part of its argument.
      @param    {Number}  The numeric value whose fractional part is to be returned
      @returns  {Number}  The fractional part of its numeric argument
     */
    'frac': function(x) {
      return x - this.trunc(x);
    },

    /**
      Returns the natural logarithm (base e) of its argument.
      @param    {Number}  The numeric value whose natural log is to be returned
      @returns  {Number}  The natural log of its numeric argument
     */
    'ln': function(x) {
      return Math.log(x);
    },

    /**
      Returns the base 10 logarithm of its argument. Note: We override Math.log
      to provide the base 10 log here. Use ln() for the natural log.
      @param    {Number}  The numeric value base 10 log is to be returned
      @returns  {Number}  The base 10 log of its numeric argument
     */
    'log': function(x) {
      return Math.log(x) / Math.LN10;
    },

    /**
      Coerces its argument to a numeric value.
      @param    {Object}  The argument to be coerced to a number
      @returns  {Number}  The converted numeric value
     */
    'number': function(x) {
      return Number(x);
    },

    /**
      Random number generator. Override of Math.random() to provide more flexibility.
      random()        -- Generates a random number in the range [0,1).
      random(max)     -- Generates a random number in the range [0,max).
      random(min,max) -- Generates a random number in the range [min,max).
      @param    {Number}  x1 -- If present alone, the maximum value of the random number.
                                If first of two argument, the minimum value of the random number.
      @param    {Number}  x2 -- The maximum value of the random number.
      @returns  {Number}  The generated random number
     */
    'random': function(x1,x2) {
      // random()
      if( SC.none(x1))
        return Math.random();
      // random(max)
      if( SC.none(x2))
        return x1 * Math.random();
      // random(min,max)
      return x1 + (x2 - x1) * Math.random();
    },

    /**
      Rounds a number to the nearest integer or specified decimal place.
      @param    {Number}  x -- The number to be rounded
      @param    {Number}  n -- {optional} The number of decimal places to round to (default 0).
      @returns  {Number}  The rounded result
     */
    'round': function(x,n) {
      if( SC.none(n))
        return Math.round(x);
      var npow = Math.pow(10, this.trunc(n));
      return Math.round(npow * x) / npow;
    },

    /**
      Coerces its argument to a string value.
      @param    {Object}  The argument to be coerced to a string
      @returns  {String}  The converted string value
     */
    'string': function(x) {
      return String(x);
    },

    /**
      Returns the integer part of its argument.
      @param    {Number}  The numeric value whose integer part is to be returned
      @returns  {Number}  The integer part of its numeric argument
     */
    'trunc': function(x) {
      return x < 0 ? Math.ceil(x) : Math.floor(x);
    },

    /**
      Returns a string treating the argument as seconds in an epoch
      @param    {Number}  A number of seconds in the epoch beginning Jan 1, 1970
      @returns  {String}  A date
     */
    'secondsToDate': function(x) {
      var tDate = new Date( x * 1000);
      return tDate.toLocaleDateString();
    },

    /**
      Returns the month corresponding to the given date
      @param    {String|Number}  A date string or number of seconds in epoch
      @returns  {String}  The month for the given date
     */
    'month': function(x) {
      if( DG.isFinite(x))
        x *= 1000;
      var tDate = new Date( x);
      switch( tDate.getMonth()) {
        case 0:
          return 'January';
        case 1:
          return 'February';
        case 2:
          return 'March';
        case 3:
          return 'April';
        case 4:
          return 'May';
        case 5:
          return 'June';
        case 6:
          return 'July';
        case 7:
          return 'August';
        case 8:
          return 'September';
        case 9:
          return 'October';
        case 10:
          return 'November';
        case 11:
          return 'December';
      };
    }
  },

  /**
    Property which provides meta-data about the functions supported by the '_fns' property.
    @property   {Object}  Map of name {String} to {Object}.
   */
  _fnsArgs: {
    'boolean': { min:1, max:1 },
    'frac': { min:1, max:1 },
    'ln': { min:1, max:1 },
    'log': { min:1, max:1 },
    'number': { min:1, max:1 },
    'random': { min:0, max:2 },
    'round': { min:1, max:2 },
    'string': { min:1, max:1 },
    'trunc': { min:1, max:1 },
    'secondsToDate': { min:1, max:1 },
    'month': { min:1, max:1 }
  },

  /**
    Property which provides meta-data about the functions supported by the JavaScript Math class.
    @property   {Object}  Map of name {String} to {Object}.
   */
  _MathArgs: {
    'abs': { min:1, max:1 },
    'acos': { min:1, max:1 },
    'asin': { min:1, max:1 },
    'atan': { min:1, max:1 },
    'atan2': { min:2, max:2 },
    'ceil': { min:1, max:1 },
    'cos': { min:1, max:1 },
    'exp': { min:1, max:1 },
    'floor': { min:1, max:1 },
    //'log': { min:1, max:1 },    // replaced by DG version
    //'max': { min:1, max:'n' },  // replaced by aggregate version
    //'min': { min:1, max:'n' },  // replaced by aggregate version
    'pow': { min:2, max:2 },
    //'random': { min:0, max:0 }, // replaced by DG version
    //'round': { min:1, max:1 },  // replaced by DG version
    'sin': { min:1, max:1 },
    'sqrt': { min:1, max:1 },
    'tan': { min:1, max:1 }
  },

  /**
    Compiles a function reference into the JavaScript code for evaluating
    the appropriate function. For the base FormulaContext, this means
    binding to global functions such as ln(), log(), round(), etc. as well
    as the standard JavaScript Math functions (sin(), cos(), atan()), etc.
    @param    {String}    iName -- The name of the function to be called.
    @param    {Array}     iArgs -- the arguments to the function
    @returns  {String}    The JavaScript code for calling the specified function
    @throws   {DG.FuncReferenceError} Throws DG.FuncReferenceError for function
                                      names that are not recognized.
   */
  compileFunction: function( iName, iArgs) {

    // Functions provided by built-in '_fns' property of context
    var _fns = this.get('_fns');
    if( _fns && typeof _fns[iName] === 'function') {
      checkArgs( iName, iArgs, this.get('_fnsArgs'));
      return 'c._fns.' + iName + '(' + iArgs + ')';
    }

    // Other functions of JavaScript Math object
    if( typeof Math[iName] === 'function') {
      checkArgs( iName, iArgs, this._MathArgs);
      return 'Math.' + iName + '(' + iArgs + ')';
    }

    // Functions provided by client-provided 'fns' property of context
    var fns = this.get('fns');
    if( fns && typeof fns[iName] === 'function') {
      checkArgs( iName, iArgs, this.get('fnsArgs'));
      return 'c.fns.' + iName + '(' + iArgs + ')';
    }

    return '(function(){throw new DG.FuncReferenceError(' + iName + ');})()';
  },

  /**
    Evaluates a function reference directly without compilation. For the base
    FormulaContext, this means binding to global functions such as ln(), log(),
    round(), etc. as well as the standard JavaScript Math functions (sin(), cos(),
    atan(), etc.).
    @param    {String}    iName -- The name of the function to be called.
    @param    {Array}     iArgs -- the arguments to the function
    @returns  {String}    The JavaScript code for calling the specified function
    @throws   {DG.FuncReferenceError} Throws DG.FuncReferenceError for function
                                      names that are not recognized.
   */
  evaluateFunction: function( iName, iArgs) {

    // Functions provided by built-in '_fns' property of context
    var _fns = this.get('_fns');
    if( _fns && typeof _fns[iName] === 'function') {
      checkArgs( iName, iArgs, this.get('_fnsArgs'));
      return _fns[iName].apply( _fns, iArgs);
    }

    // Other functions of JavaScript Math object
    if( typeof Math[iName] === 'function') {
      checkArgs( iName, iArgs, this._MathArgs);
      return Math[iName].apply( Math, iArgs);
    }

    // Functions provided by client-provided 'fns' property of context
    var fns = this.get('fns');
    if( fns && typeof fns[iName] === 'function') {
      checkArgs( iName, iArgs, this.get('fnsArgs'));
      return fns[iName].apply( fns, iArgs);
    }

    throw new DG.FuncReferenceError( iName);
  }

  }; // end of closure return statement

}()));

/**
  Returns an evaluable JavaScript function that returns the value
  of the specified expression. The function created is of the form:
    function( c, e) { return iExpression };
  where c is the formula context/compile context and e is the evaluation context.
  @returns  {Function}  The evaluable function
 */
DG.FormulaContext.createContextFunction = function( iExpression) {
  /* jslint evil:true */  // allow use of the Function constructor
  return new Function('c', 'e', 'return ' + iExpression);
};

/**
  Adds the names of the functions in iModule to the fnNames property,
  for use in creating the function popup menu. The properties of the
  iModule object are assumed to be functions, and the names are taken
  from the names of the properties. This will need some adjustment when
  localization is taken into account.
  @param  {Object}  iModule -- Properties are functions
 */
DG.FormulaContext.registerFnModule = function( iModule) {
  var fnNames = DG.FormulaContext.prototype.fnNames;
  // Use push.apply() to push the array elements instead of the array.
  fnNames.push.apply( fnNames, DG.ObjectMap.keys( iModule));
  fnNames.sort();
};

/**
  Register the base function modules built into the context.
 */
DG.FormulaContext.registerFnModule( DG.FormulaContext.prototype._fns);
DG.FormulaContext.registerFnModule( DG.FormulaContext.prototype._MathArgs);

/**
  Returns an array of function names for all the supported functions,
  along with empty parentheses after the function name.
  @returns  {Array of String}   The array of function names (with parentheses)
 */
DG.FormulaContext.getFunctionNamesWithParentheses = function() {
  var names = DG.FormulaContext.prototype.fnNames;
  return names.map( function( iName) {
                      return iName + "()";
                    });
};

<|endoftext|>


<commit_before>/*
* Copyright 2016 Avi Kelman <patcherton.fixesthings@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

function fileNameFromHREF(name) {
    name = name.substring(0, (name.indexOf("#") == -1) ? name.length : name.indexOf("#"));
    name = name.substring(0, (name.indexOf("?") == -1) ? name.length : name.indexOf("?"));
    name = name.substring(name.lastIndexOf("/") + 1, name.length);
    name = decodeURIComponent(escape(name)); // decode utf8
    if (name.length == 0) {
        name = "untitled.txt";
    }
    return name;
}

function saveData(data, dataType, fileName) {
    data = data.replace(/([^\r])\n/g, "$1\r\n");
    var blob = new Blob([data], { type: dataType });
    navigator.msSaveOrOpenBlob(blob, fileName);
}

function saveBodyText() {
    var data = document.body.textContent;
    saveData(data, "text/plain", fileNameFromHREF(document.location.href));
}

browser.runtime.onMessage.addListener(function (msg) {
    if (msg == "saveText") {
        saveBodyText();
    }
});
<commit_msg>should be doing Save instead of SaveOrOpen<commit_after>/*
* Copyright 2016 Avi Kelman <patcherton.fixesthings@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

function fileNameFromHREF(name) {
    name = name.substring(0, (name.indexOf("#") == -1) ? name.length : name.indexOf("#"));
    name = name.substring(0, (name.indexOf("?") == -1) ? name.length : name.indexOf("?"));
    name = name.substring(name.lastIndexOf("/") + 1, name.length);
    name = decodeURIComponent(escape(name)); // decode utf8
    if (name.length == 0) {
        name = "untitled.txt";
    }
    return name;
}

function saveData(data, dataType, fileName) {
    data = data.replace(/([^\r])\n/g, "$1\r\n");
    var blob = new Blob([data], { type: dataType });
    navigator.msSaveBlob(blob, fileName);
}

function saveBodyText() {
    var data = document.body.textContent;
    saveData(data, "text/plain", fileNameFromHREF(document.location.href));
}

browser.runtime.onMessage.addListener(function (msg) {
    if (msg == "saveText") {
        saveBodyText();
    }
});
<|endoftext|>


<commit_before><commit_msg>Remove modal loading dialog<commit_after><|endoftext|>


<commit_before>angular.module('starter.controllers', [])

.controller('MainCtrl', function($scope, $ionicModal, $ionicPlatform, $cordovaVibration) {
  $scope.Data = {
    stereoEffect: false,
    landscapeMode: false,
    moveStarFighter: false
  };

  $ionicModal.fromTemplateUrl('templates/settings.html', {
    scope: $scope,
    animation: 'slide-in-up'
  }).then(function(modal) {
    $scope.settingsModal = modal;
  });
  $scope.openSettingsModal = function() {
    $scope.settingsModal.show();
  };
  $scope.closeSettingsModal = function() {
    $scope.settingsModal.hide();
  };

  $scope.Move = function() {
    Vibrate(50);
    $scope.Data.moveStarFighter = !$scope.Data.moveStarFighter;
  };

  function Vibrate(input) {
    $ionicPlatform.ready(function() {
      $cordovaVibration.vibrate(input);
    });
  };

  function readDeviceOrientation() {
    if (Math.abs(window.orientation) === 90) {
      // Landscape
      $scope.Data.landscapeMode = true;
      $scope.$apply();
    } else {
      // Portrait
      $scope.Data.landscapeMode = false;
      $scope.Data.stereoEffect = false;
      $scope.Data.moveStarFighter = false;
      $scope.$apply();
    }
  };
  window.addEventListener('orientationchange', readDeviceOrientation, false);

})
<commit_msg>Added vibration on device orientation change<commit_after>angular.module('starter.controllers', [])

.controller('MainCtrl', function($scope, $ionicModal, $ionicPlatform, $cordovaVibration) {
  $scope.Data = {
    stereoEffect: false,
    landscapeMode: false,
    moveStarFighter: false
  };

  $ionicModal.fromTemplateUrl('templates/settings.html', {
    scope: $scope,
    animation: 'slide-in-up'
  }).then(function(modal) {
    $scope.settingsModal = modal;
  });
  $scope.openSettingsModal = function() {
    $scope.settingsModal.show();
  };
  $scope.closeSettingsModal = function() {
    $scope.settingsModal.hide();
  };

  $scope.Move = function() {
    Vibrate(50);
    $scope.Data.moveStarFighter = !$scope.Data.moveStarFighter;
  };

  function Vibrate(input) {
    $ionicPlatform.ready(function() {
      $cordovaVibration.vibrate(input);
    });
  };

  function readDeviceOrientation() {
    if (Math.abs(window.orientation) === 90) {
      // Landscape
      Vibrate(50);
      $scope.Data.landscapeMode = true;
      $scope.$apply();
    } else {
      // Portrait
      Vibrate(50);
      $scope.Data.landscapeMode = false;
      $scope.Data.stereoEffect = false;
      $scope.Data.moveStarFighter = false;
      $scope.$apply();
    }
  };
  window.addEventListener('orientationchange', readDeviceOrientation, false);

})
<|endoftext|>


<commit_before>// Globals.
var GM_config = GM_getConfig();

(function() {
// Override some built-in functions, with a closure reference to the original
// function, to either handle or delegate the call.
var _origShowView = showView;
showView = function(aView) {
  if ('userscripts' == aView) {
    greasemonkeyAddons.showView();
  } else {
    _origShowView(aView);
  }
};

var _origBuildContextMenu = buildContextMenu;
buildContextMenu = function(aEvent) {
  if ('userscripts' == gView) {
    greasemonkeyAddons.buildContextMenu(aEvent);
  } else {
    _origBuildContextMenu(aEvent);
  }
};

// Set up an "observer" on the config, to keep the displayed items up to date
// with their actual state.
var config = GM_config;
window.addEventListener("load", function() {
  config.addObserver(observer);
}, false);
window.addEventListener("unload", function() {
  config.removeObserver(observer);
}, false);

var observer = {
  notifyEvent: function(script, event, data) {
    if (event == "install") {
      var item = greasemonkeyAddons.addScriptToList(script);
      gExtensionsView.selectedItem = item;
      return;
    }

    var listbox = gExtensionsView;
    var node;
    var scriptId = script.namespace + script.name;
    for (var i = 0; node = listbox.childNodes[i]; i++) {
      if (node.getAttribute('addonId') == scriptId) {
        break;
      }
    }
    if (!node) return;

    switch (event) {
      case "edit-enabled":
        node.setAttribute('isDisabled', !data);
        break;
      case "uninstall":
        listbox.removeChild(node);
        break;
      case "move":
        listbox.removeChild(node);
        listbox.insertBefore(node, listbox.childNodes[data]);
        break;
    }
  }
};
})();

// Set event listeners.
window.addEventListener('load', function() {
  greasemonkeyAddons.onAddonSelect();
  gExtensionsView.addEventListener(
      'select', greasemonkeyAddons.onAddonSelect, false);

  // Work-around for Stylish compatibility, which does not update gView in
  // its overridden showView() function.
  var stylishRadio = document.getElementById('userstyles-view');
  if (stylishRadio) {
    stylishRadio.addEventListener(
        'command',
        function() { gView = 'userstyles' },
        false);
  }
}, false);

var greasemonkeyAddons = {
  showView: function() {
    if ('userscripts' == gView) return;
    updateLastSelected('userscripts');
    gView='userscripts';

    // Update any possibly modified scripts.
    GM_config.updateModifiedScripts();

    // Hide the native controls that don't work in the user scripts view.
    function $(id) { return document.getElementById(id); }
    function hide(el) { el=$(el); el && (el.hidden=true); }
    var elementIds=[
      'searchPanel', 'installFileButton', 'checkUpdatesAllButton',
      'skipDialogButton', 'themePreviewArea', 'themeSplitter',
      'showUpdateInfoButton', 'hideUpdateInfoButton',
      'installUpdatesAllButton',
      // Stylish injects these elements.
      'copy-style-info', 'new-style'];
    elementIds.forEach(hide);

    var getMore = document.getElementById('getMore');
    getMore.setAttribute('getMoreURL', 'http://userscripts.org/');
    getMore.hidden = false;
    getMore.value = 'Get User Scripts';

    greasemonkeyAddons.fillList();
    gExtensionsView.selectedItem = gExtensionsView.children[0];
    // The setTimeout() here is for timing, to make sure the selection above
    // has really happened.
    setTimeout(greasemonkeyAddons.onAddonSelect, 0);
  },

  fillList: function() {
    var config = GM_config;
    var listbox = gExtensionsView;

    // Remove any pre-existing contents.
    while (listbox.firstChild) {
      listbox.removeChild(listbox.firstChild);
    }

    // Add a list item for each script.
    for (var i = 0, script = null; script = config.scripts[i]; i++) {
      greasemonkeyAddons.addScriptToList(script);
    }
  },

  listitemForScript: function(script) {
    var item = document.createElement('richlistitem');
    item.setAttribute('class', 'userscript');
    // Fake this for now.
    var id = script.namespace + script.name;
    // Setting these attributes inherits the values into the same place they
    // would go for extensions.
    item.setAttribute('addonId', id);
    item.setAttribute('name', script.name);
    item.setAttribute('description', script.description);
    item.setAttribute('id', 'urn:greasemonkey:item:'+id);
    item.setAttribute('isDisabled', !script.enabled);
    // These hide extension-specific bits we don't want to display.
    item.setAttribute('blocklisted', 'false');
    item.setAttribute('blocklistedsoft', 'false');
    item.setAttribute('compatible', 'true');
    item.setAttribute('locked', 'false');
    item.setAttribute('providesUpdatesSecurely', 'true');
    item.setAttribute('satisfiesDependencies', 'true');
    item.setAttribute('type', nsIUpdateItem.TYPE_EXTENSION);
    return item;
  },

  addScriptToList: function(script, beforeNode) {
    var item = greasemonkeyAddons.listitemForScript(script);
    gExtensionsView.insertBefore(item, beforeNode || null);
    return item;
  },

  findSelectedScript: function() {
    if (!gExtensionsView.selectedItem) return null;
    var scripts = GM_config.scripts;
    var selectedScriptId = gExtensionsView.selectedItem.getAttribute('addonId');
    for (var i = 0, script = null; script = scripts[i]; i++) {
      if (selectedScriptId == script.namespace + script.name) {
        return script;
      }
    }
    return null;
  },

  onAddonSelect: function(aEvent) {
    // We do all this work here, because the elements we want to change do
    // not exist until the item is selected.

    if ('userscripts' != gView) return;
    var script = greasemonkeyAddons.findSelectedScript();

    // Remove/change the anonymous nodes we don't want.
    var item = gExtensionsView.selectedItem;
    var button;

    // Replace 'preferences' with 'edit'.
    button = item.ownerDocument.getAnonymousElementByAttribute(
        item, 'command', 'cmd_options');
    if (!button) return;
    button.setAttribute('label', 'Edit');
    button.setAttribute('accesskey', 'E');
    button.setAttribute('tooltiptext', 'Edit the selected User Script');
    button.setAttribute('command', 'cmd_userscript_edit');
    button.setAttribute('disabled', 'false');

    // Rewire enable, disable, uninstall.
    button = item.ownerDocument.getAnonymousElementByAttribute(
        item, 'command', 'cmd_enable');
    if (!button) return;
    button.setAttribute('tooltiptext', 'Enable the selected User Script');
    button.setAttribute('command', 'cmd_userscript_enable');
    button.setAttribute('disabled', 'false');

    button = item.ownerDocument.getAnonymousElementByAttribute(
        item, 'command', 'cmd_disable');
    if (!button) return;
    button.setAttribute('tooltiptext', 'Disable the selected User Script');
    button.setAttribute('command', 'cmd_userscript_disable');
    button.setAttribute('disabled', 'false');

    button = item.ownerDocument.getAnonymousElementByAttribute(
        item, 'command', 'cmd_uninstall');
    if (!button) return;
    button.setAttribute('tooltiptext', 'Uninstall the selected User Script');
    button.setAttribute('command', 'cmd_userscript_uninstall');
    button.setAttribute('disabled', 'false');
  },

  doCommand: function(command) {
    var script = greasemonkeyAddons.findSelectedScript();
    if (!script) {
      alert('Crap, something went wrong!');
      return;
    }

    var selectedListitem = gExtensionsView.selectedItem;
    switch (command) {
    case 'cmd_userscript_edit':
      GM_openInEditor(script);
      break;
    case 'cmd_userscript_enable':
      script.enabled = true;
      break;
    case 'cmd_userscript_disable':
      script.enabled = false;
      break;
    case 'cmd_userscript_move_down':
      GM_config.move(script, 1);
      break;
    case 'cmd_userscript_move_bottom':
      GM_config.move(script, GM_config.scripts.length);
      break;
    case 'cmd_userscript_move_up':
      GM_config.move(script, -1);
      break;
    case 'cmd_userscript_move_top':
      GM_config.move(script, -1 * GM_config.scripts.length);
      break;
    case 'cmd_userscript_sort':
      function scriptCmp(a, b) { return a.name < b.name ? -1 : 1; }
      GM_config._scripts.sort(scriptCmp);
      GM_config._save();
      greasemonkeyAddons.fillList();
      break;
    case 'cmd_userscript_uninstall':
      GM_config.uninstall(script);
      break;
    }
  },

  buildContextMenu: function(aEvent) {
    var script = greasemonkeyAddons.findSelectedScript();
    if (!script) {
      alert('Crap, something went wrong!');
      return;
    }

    var popup = document.getElementById('addonContextMenu');
    while (popup.hasChildNodes()) {
      popup.removeChild(popup.firstChild);
    }

    function addMenuItem(label, command) {
      var menuitem = document.createElement('menuitem');
      menuitem.setAttribute('label', label);
      menuitem.setAttribute('command', command);
      popup.appendChild(menuitem);
    }

    addMenuItem('Edit', 'cmd_userscript_edit');
    if (script.enabled) {
      addMenuItem('Disable', 'cmd_userscript_disable');
    } else {
      addMenuItem('Enable', 'cmd_userscript_enable');
    }
    addMenuItem('Uninstall', 'cmd_userscript_uninstall');

    popup.appendChild(document.createElement('menuseparator'));

    addMenuItem('Move Up', 'cmd_userscript_move_up');
    addMenuItem('Move Down', 'cmd_userscript_move_down');
    addMenuItem('Move To Top', 'cmd_userscript_move_top');
    addMenuItem('Move To Bottom', 'cmd_userscript_move_bottom');

    popup.appendChild(document.createElement('menuseparator'));

    addMenuItem('Sort Scripts', 'cmd_userscript_sort');
  }
};
<commit_msg>In onAddonSelect(), verify that something really is selected.<commit_after>// Globals.
var GM_config = GM_getConfig();

(function() {
// Override some built-in functions, with a closure reference to the original
// function, to either handle or delegate the call.
var _origShowView = showView;
showView = function(aView) {
  if ('userscripts' == aView) {
    greasemonkeyAddons.showView();
  } else {
    _origShowView(aView);
  }
};

var _origBuildContextMenu = buildContextMenu;
buildContextMenu = function(aEvent) {
  if ('userscripts' == gView) {
    greasemonkeyAddons.buildContextMenu(aEvent);
  } else {
    _origBuildContextMenu(aEvent);
  }
};

// Set up an "observer" on the config, to keep the displayed items up to date
// with their actual state.
var config = GM_config;
window.addEventListener("load", function() {
  config.addObserver(observer);
}, false);
window.addEventListener("unload", function() {
  config.removeObserver(observer);
}, false);

var observer = {
  notifyEvent: function(script, event, data) {
    if (event == "install") {
      var item = greasemonkeyAddons.addScriptToList(script);
      gExtensionsView.selectedItem = item;
      return;
    }

    var listbox = gExtensionsView;
    var node;
    var scriptId = script.namespace + script.name;
    for (var i = 0; node = listbox.childNodes[i]; i++) {
      if (node.getAttribute('addonId') == scriptId) {
        break;
      }
    }
    if (!node) return;

    switch (event) {
      case "edit-enabled":
        node.setAttribute('isDisabled', !data);
        break;
      case "uninstall":
        listbox.removeChild(node);
        break;
      case "move":
        listbox.removeChild(node);
        listbox.insertBefore(node, listbox.childNodes[data]);
        break;
    }
  }
};
})();

// Set event listeners.
window.addEventListener('load', function() {
  greasemonkeyAddons.onAddonSelect();
  gExtensionsView.addEventListener(
      'select', greasemonkeyAddons.onAddonSelect, false);

  // Work-around for Stylish compatibility, which does not update gView in
  // its overridden showView() function.
  var stylishRadio = document.getElementById('userstyles-view');
  if (stylishRadio) {
    stylishRadio.addEventListener(
        'command',
        function() { gView = 'userstyles' },
        false);
  }
}, false);

var greasemonkeyAddons = {
  showView: function() {
    if ('userscripts' == gView) return;
    updateLastSelected('userscripts');
    gView='userscripts';

    // Update any possibly modified scripts.
    GM_config.updateModifiedScripts();

    // Hide the native controls that don't work in the user scripts view.
    function $(id) { return document.getElementById(id); }
    function hide(el) { el=$(el); el && (el.hidden=true); }
    var elementIds=[
      'searchPanel', 'installFileButton', 'checkUpdatesAllButton',
      'skipDialogButton', 'themePreviewArea', 'themeSplitter',
      'showUpdateInfoButton', 'hideUpdateInfoButton',
      'installUpdatesAllButton',
      // Stylish injects these elements.
      'copy-style-info', 'new-style'];
    elementIds.forEach(hide);

    var getMore = document.getElementById('getMore');
    getMore.setAttribute('getMoreURL', 'http://userscripts.org/');
    getMore.hidden = false;
    getMore.value = 'Get User Scripts';

    greasemonkeyAddons.fillList();
    gExtensionsView.selectedItem = gExtensionsView.children[0];
    // The setTimeout() here is for timing, to make sure the selection above
    // has really happened.
    setTimeout(greasemonkeyAddons.onAddonSelect, 0);
  },

  fillList: function() {
    var config = GM_config;
    var listbox = gExtensionsView;

    // Remove any pre-existing contents.
    while (listbox.firstChild) {
      listbox.removeChild(listbox.firstChild);
    }

    // Add a list item for each script.
    for (var i = 0, script = null; script = config.scripts[i]; i++) {
      greasemonkeyAddons.addScriptToList(script);
    }
  },

  listitemForScript: function(script) {
    var item = document.createElement('richlistitem');
    item.setAttribute('class', 'userscript');
    // Fake this for now.
    var id = script.namespace + script.name;
    // Setting these attributes inherits the values into the same place they
    // would go for extensions.
    item.setAttribute('addonId', id);
    item.setAttribute('name', script.name);
    item.setAttribute('description', script.description);
    item.setAttribute('id', 'urn:greasemonkey:item:'+id);
    item.setAttribute('isDisabled', !script.enabled);
    // These hide extension-specific bits we don't want to display.
    item.setAttribute('blocklisted', 'false');
    item.setAttribute('blocklistedsoft', 'false');
    item.setAttribute('compatible', 'true');
    item.setAttribute('locked', 'false');
    item.setAttribute('providesUpdatesSecurely', 'true');
    item.setAttribute('satisfiesDependencies', 'true');
    item.setAttribute('type', nsIUpdateItem.TYPE_EXTENSION);
    return item;
  },

  addScriptToList: function(script, beforeNode) {
    var item = greasemonkeyAddons.listitemForScript(script);
    gExtensionsView.insertBefore(item, beforeNode || null);
    return item;
  },

  findSelectedScript: function() {
    if (!gExtensionsView.selectedItem) return null;
    var scripts = GM_config.scripts;
    var selectedScriptId = gExtensionsView.selectedItem.getAttribute('addonId');
    for (var i = 0, script = null; script = scripts[i]; i++) {
      if (selectedScriptId == script.namespace + script.name) {
        return script;
      }
    }
    return null;
  },

  onAddonSelect: function(aEvent) {
    // We do all this work here, because the elements we want to change do
    // not exist until the item is selected.

    if (!gExtensionsView.selectedItem) return;
    if ('userscripts' != gView) return;
    var script = greasemonkeyAddons.findSelectedScript();

    // Remove/change the anonymous nodes we don't want.
    var item = gExtensionsView.selectedItem;
    var button;

    // Replace 'preferences' with 'edit'.
    button = item.ownerDocument.getAnonymousElementByAttribute(
        item, 'command', 'cmd_options');
    if (!button) return;
    button.setAttribute('label', 'Edit');
    button.setAttribute('accesskey', 'E');
    button.setAttribute('tooltiptext', 'Edit the selected User Script');
    button.setAttribute('command', 'cmd_userscript_edit');
    button.setAttribute('disabled', 'false');

    // Rewire enable, disable, uninstall.
    button = item.ownerDocument.getAnonymousElementByAttribute(
        item, 'command', 'cmd_enable');
    if (!button) return;
    button.setAttribute('tooltiptext', 'Enable the selected User Script');
    button.setAttribute('command', 'cmd_userscript_enable');
    button.setAttribute('disabled', 'false');

    button = item.ownerDocument.getAnonymousElementByAttribute(
        item, 'command', 'cmd_disable');
    if (!button) return;
    button.setAttribute('tooltiptext', 'Disable the selected User Script');
    button.setAttribute('command', 'cmd_userscript_disable');
    button.setAttribute('disabled', 'false');

    button = item.ownerDocument.getAnonymousElementByAttribute(
        item, 'command', 'cmd_uninstall');
    if (!button) return;
    button.setAttribute('tooltiptext', 'Uninstall the selected User Script');
    button.setAttribute('command', 'cmd_userscript_uninstall');
    button.setAttribute('disabled', 'false');
  },

  doCommand: function(command) {
    var script = greasemonkeyAddons.findSelectedScript();
    if (!script) {
      alert('Crap, something went wrong!');
      return;
    }

    var selectedListitem = gExtensionsView.selectedItem;
    switch (command) {
    case 'cmd_userscript_edit':
      GM_openInEditor(script);
      break;
    case 'cmd_userscript_enable':
      script.enabled = true;
      break;
    case 'cmd_userscript_disable':
      script.enabled = false;
      break;
    case 'cmd_userscript_move_down':
      GM_config.move(script, 1);
      break;
    case 'cmd_userscript_move_bottom':
      GM_config.move(script, GM_config.scripts.length);
      break;
    case 'cmd_userscript_move_up':
      GM_config.move(script, -1);
      break;
    case 'cmd_userscript_move_top':
      GM_config.move(script, -1 * GM_config.scripts.length);
      break;
    case 'cmd_userscript_sort':
      function scriptCmp(a, b) { return a.name < b.name ? -1 : 1; }
      GM_config._scripts.sort(scriptCmp);
      GM_config._save();
      greasemonkeyAddons.fillList();
      break;
    case 'cmd_userscript_uninstall':
      GM_config.uninstall(script);
      break;
    }
  },

  buildContextMenu: function(aEvent) {
    var script = greasemonkeyAddons.findSelectedScript();
    if (!script) {
      alert('Crap, something went wrong!');
      return;
    }

    var popup = document.getElementById('addonContextMenu');
    while (popup.hasChildNodes()) {
      popup.removeChild(popup.firstChild);
    }

    function addMenuItem(label, command) {
      var menuitem = document.createElement('menuitem');
      menuitem.setAttribute('label', label);
      menuitem.setAttribute('command', command);
      popup.appendChild(menuitem);
    }

    addMenuItem('Edit', 'cmd_userscript_edit');
    if (script.enabled) {
      addMenuItem('Disable', 'cmd_userscript_disable');
    } else {
      addMenuItem('Enable', 'cmd_userscript_enable');
    }
    addMenuItem('Uninstall', 'cmd_userscript_uninstall');

    popup.appendChild(document.createElement('menuseparator'));

    addMenuItem('Move Up', 'cmd_userscript_move_up');
    addMenuItem('Move Down', 'cmd_userscript_move_down');
    addMenuItem('Move To Top', 'cmd_userscript_move_top');
    addMenuItem('Move To Bottom', 'cmd_userscript_move_bottom');

    popup.appendChild(document.createElement('menuseparator'));

    addMenuItem('Sort Scripts', 'cmd_userscript_sort');
  }
};
<|endoftext|>


<commit_before>/*
 * @package jsDAV
 * @subpackage DAV
 * @copyright Copyright(c) 2011 Ajax.org B.V. <info AT ajax DOT org>
 * @author Mike de Boer <info AT mikedeboer DOT nl>
 * @license http://github.com/mikedeboer/jsDAV/blob/master/LICENSE MIT License
 */
"use strict";

/**
 * Main Exception class.
 *
 * This class defines a getHTTPCode method, which should return the appropriate
 * HTTP code for the Exception occured.
 * The default for this is 500.
 *
 * This class also allows you to generate custom xml data for your exceptions.
 * This will be displayed in the 'error' element in the failing response.
 */
exports.jsDAV_Exception = function(msg, extra) {
    /**
     * The HTTP statuscode for this exception
     *
     * @return int
     */
    this.code    = 500;

    /**
     * The type of this exception, usually the same as the class name.
     */
    this.type    = "jsDAV_Exception";

    /**
     * The corresponding message for this exception
     *
     * @return int
     */
    this.message = msg || this.type;

    /**
     * This method allows the exception to return any extra HTTP response headers.
     *
     * The headers must be returned as an array.
     *
     * @return array
     */
    this.getHTTPHeaders = function(handler, cbheaders) { cbheaders(null, {}); };

    /**
     * This method allows the exception to include additonal information into the
     * WebDAV error response
     *
     * @param {jsDAV_Handler} handler
     * @param {DOMElement}    errorNode
     * @return void
     */
    this.serialize = function(handler, errorNode) {return errorNode;};

    this.toString = function(handler, cbtostring) {
        var self   = this;
        var headers = "";
        if (handler) {
            headers = "Headers: ";
            this.getHTTPHeaders(handler, function(err, h) {
                for (var i in h)
                    headers += "\t" + i + ": " + h[i] + "\n";
                afterHeaders();
            });
        }
        else {
            afterHeaders();
        }

        function afterHeaders() {
            var msg = headers + "HTTP code: " + self.code + "\n"
                + "Exception type: " + self.type + "\n"
                + "Message: " + self.message;
            return cbtostring ? cbtostring(null, msg) : msg;
        }
    };
};
exports.jsDAV_Exception.prototype = new Error();

/**
 * BadRequest
 *
 * The BadRequest is thrown when the user submitted an invalid HTTP request
 * BadRequest
 */
exports.jsDAV_Exception_BadRequest = function(msg, extra) {
    this.code    = 400;
    this.type    = "jsDAV_Exception_BadRequest";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_BadRequest.prototype = new exports.jsDAV_Exception();

/**
 * Conflict
 *
 * A 409 Conflict is thrown when a user tried to make a directory over an existing
 * file or in a parent directory that doesn't exist.
 */
exports.jsDAV_Exception_Conflict = function(msg, extra) {
    this.code    = 409;
    this.type    = "jsDAV_Exception_Conflict";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_Conflict.prototype = new exports.jsDAV_Exception();

/**
 * Locked
 *
 * The 423 is thrown when a client tried to access a resource that was locked,
 * without supplying a valid lock token
 */
exports.jsDAV_Exception_Locked = function(lock) {
    this.code = 423;
    this.type = this.message = "jsDAV_Exception_Locked";
    this.lock = lock;

    this.serialize = function(handler, errorNode) {
        if (this.lock) {
            return errorNode + "<d:lock-token-submitted><d:href>" + this.lock.uri
                + "</d:href></d:lock-token-submitted>";
        }
        return errorNode;
    };
};
exports.jsDAV_Exception_Locked.prototype = new exports.jsDAV_Exception_Locked();

/**
 * ConflictingLock
 *
 * Similar to Exception_Locked, this exception thrown when a LOCK request
 * was made, on a resource which was already locked
 */
exports.jsDAV_Exception_ConflictingLock = function(lock) {
    this.type = this.message = "jsDAV_Exception_ConflictingLock";
    this.lock = lock;

    this.serialize = function(handler, errorNode) {
        if (this.lock) {
            return errorNode + "<d:no-conflicting-lock><d:href>" + this.lock.uri
                + "</d:href></d:no-conflicting-lock>";
        }
        return errorNode;
    };
};
exports.jsDAV_Exception_ConflictingLock.prototype = new exports.jsDAV_Exception_Locked();

/**
 * FileNotFound
 *
 * This Exception is thrown when a Node couldn't be found. It returns HTTP error
 * code 404
 */
exports.jsDAV_Exception_FileNotFound = function(msg, extra) {
    this.code    = 404;
    this.type    = "jsDAV_Exception_FileNotFound";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_FileNotFound.prototype = new exports.jsDAV_Exception();

/**
 * Forbidden
 *
 * This exception is thrown whenever a user tries to do an operation he's not
 * allowed to
 */
exports.jsDAV_Exception_Forbidden = function(msg, extra) {
    this.code    = 403;
    this.type    = "jsDAV_Exception_Forbidden";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_Forbidden.prototype = new exports.jsDAV_Exception();

/**
 * InsufficientStorage
 *
 * This Exception can be thrown, when for example a harddisk is full or a quota
 * is exceeded
 */
exports.jsDAV_Exception_InsufficientStorage = function(msg, extra) {
    this.code    = 507;
    this.type    = "jsDAV_Exception_InsufficientStorage";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_InsufficientStorage.prototype = new exports.jsDAV_Exception();

/**
 * InvalidResourceType
 *
 * This exception is thrown when the user tried to create a new collection, with
 * a special resourcetype value that was not recognized by the server.
 *
 * See RFC5689 section 3.3
 */
exports.jsDAV_Exception_InvalidResourceType = function(msg, extra) {
    this.type    = "jsDAV_Exception_InvalidResourceType";
    this.message = msg || this.type;

    this.serialize = function(handler, errorNode) {
        return errorNode + "<d:valid-resourcetype />";
    };
};
exports.jsDAV_Exception_InvalidResourceType.prototype = new exports.jsDAV_Exception_Forbidden();

/**
 * LockTokenMatchesRequestUri
 *
 * This exception is thrown by UNLOCK if a supplied lock-token is invalid
 */
exports.jsDAV_Exception_LockTokenMatchesRequestUri = function() {
    this.type    = "jsDAV_Exception_LockTokenMatchesRequestUri";
    this.message = "The locktoken supplied does not match any locks on this entity";

    this.serialize = function(handler, errorNode) {
        return errorNode + "<d:lock-token-matches-request-uri />";
    };
};
exports.jsDAV_Exception_LockTokenMatchesRequestUri.prototype = new exports.jsDAV_Exception_Conflict();

/**
 * MethodNotAllowed
 *
 * The 405 is thrown when a client tried to create a directory on an already existing directory
 */
exports.jsDAV_Exception_MethodNotAllowed = function(msg, extra) {
    this.code    = 405;
    this.type    = "jsDAV_Exception_MethodNotAllowed";
    this.message = msg || this.type;

    this.getHTTPHeaders = function(handler, cbmethods) {
        handler.getAllowedMethods(handler.getRequestUri(), function(err, methods) {
            cbmethods(err, {
                "Allow" : methods.join(", ").toUpperCase()
            });
        });
    };
};
exports.jsDAV_Exception_MethodNotAllowed.prototype = new exports.jsDAV_Exception();

/**
 * NotAuthenticated
 *
 * This exception is thrown when the client did not provide valid
 * authentication credentials.
 */
exports.jsDAV_Exception_NotAuthenticated = function(msg, extra) {
    this.code    = 401;
    this.type    = "jsDAV_Exception_NotAuthenticated";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_NotAuthenticated.prototype = new exports.jsDAV_Exception();

/**
 * NotImplemented
 *
 * This exception is thrown when the client tried to call an unsupported HTTP
 * method or other feature
 */
exports.jsDAV_Exception_NotImplemented = function(msg, extra) {
    this.code    = 501;
    this.type    = "jsDAV_Exception_NotImplemented";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_NotImplemented.prototype = new exports.jsDAV_Exception();

/**
 * PreconditionFailed
 *
 * This exception is normally thrown when a client submitted a conditional request,
 * like for example an If, If-None-Match or If-Match header, which caused the HTTP
 * request to not execute (the condition of the header failed)
 */
exports.jsDAV_Exception_PreconditionFailed = function(msg, extra) {
    this.code    = 412;
    this.type    = "jsDAV_Exception_PreconditionFailed";
    this.message = msg || this.type;
    this.header  = extra;

    this.serialize = function(handler, errorNode) {
        return this.header
            ? errorNode + "<a:header>" + this.header + "</a:header>"
            : errorNode;
    };
};
exports.jsDAV_Exception_PreconditionFailed.prototype = new exports.jsDAV_Exception();

/**
 * ReportNotImplemented
 *
 * This exception is thrown when the client requested an unknown report through
 * the REPORT method
 */
exports.jsDAV_Exception_ReportNotImplemented = function(msg, extra) {
    this.type    = "jsDAV_Exception_ReportNotImplemented";
    this.message = msg || this.type;

    this.serialize = function(handler, errorNode) {
        return errorNode + "<d:supported-report/>";
    };
};
exports.jsDAV_Exception_ReportNotImplemented.prototype = new exports.jsDAV_Exception_NotImplemented();

/**
 * RequestedRangeNotSatisfiable
 *
 * This exception is normally thrown when the user
 * request a range that is out of the entity bounds.
 */
exports.jsDAV_Exception_RequestedRangeNotSatisfiable = function(msg, extra) {
    this.code    = 416;
    this.type    = "jsDAV_Exception_RequestedRangeNotSatisfiable";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_RequestedRangeNotSatisfiable.prototype = new exports.jsDAV_Exception();

/**
 * UnSupportedMediaType
 *
 * The 415 Unsupported Media Type status code is generally sent back when the client
 * tried to call an HTTP method, with a body the server didn't understand
 */
exports.jsDAV_Exception_UnsupportedMediaType = function(msg, extra) {
    this.code    = 415;
    this.type    = "jsDAV_Exception_UnsupportedMediaType";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_UnsupportedMediaType.prototype = new exports.jsDAV_Exception();
<commit_msg>Correctly inherit error object<commit_after>/*
 * @package jsDAV
 * @subpackage DAV
 * @copyright Copyright(c) 2011 Ajax.org B.V. <info AT ajax DOT org>
 * @author Mike de Boer <info AT mikedeboer DOT nl>
 * @license http://github.com/mikedeboer/jsDAV/blob/master/LICENSE MIT License
 */
"use strict";

/**
 * Main Exception class.
 *
 * This class defines a getHTTPCode method, which should return the appropriate
 * HTTP code for the Exception occured.
 * The default for this is 500.
 *
 * This class also allows you to generate custom xml data for your exceptions.
 * This will be displayed in the 'error' element in the failing response.
 */
exports.jsDAV_Exception = function(msg, extra) {
    /**
     * The HTTP statuscode for this exception
     *
     * @return int
     */
    this.code    = 500;

    /**
     * The type of this exception, usually the same as the class name.
     */
    this.type    = "jsDAV_Exception";

    /**
     * The corresponding message for this exception
     *
     * @return int
     */
    this.message = msg || this.type;

    /**
     * This method allows the exception to return any extra HTTP response headers.
     *
     * The headers must be returned as an array.
     *
     * @return array
     */
    this.getHTTPHeaders = function(handler, cbheaders) { cbheaders(null, {}); };

    /**
     * This method allows the exception to include additonal information into the
     * WebDAV error response
     *
     * @param {jsDAV_Handler} handler
     * @param {DOMElement}    errorNode
     * @return void
     */
    this.serialize = function(handler, errorNode) {return errorNode;};

    this.toString = function(handler, cbtostring) {
        var self   = this;
        var headers = "";
        if (handler) {
            headers = "Headers: ";
            this.getHTTPHeaders(handler, function(err, h) {
                for (var i in h)
                    headers += "\t" + i + ": " + h[i] + "\n";
                afterHeaders();
            });
        }
        else {
            afterHeaders();
        }

        function afterHeaders() {
            var msg = headers + "HTTP code: " + self.code + "\n"
                + "Exception type: " + self.type + "\n"
                + "Message: " + self.message;
            return cbtostring ? cbtostring(null, msg) : msg;
        }
    };
};
require('util').inherits(exports.jsDAV_Exception, Error);

/**
 * BadRequest
 *
 * The BadRequest is thrown when the user submitted an invalid HTTP request
 * BadRequest
 */
exports.jsDAV_Exception_BadRequest = function(msg, extra) {
    this.code    = 400;
    this.type    = "jsDAV_Exception_BadRequest";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_BadRequest.prototype = new exports.jsDAV_Exception();

/**
 * Conflict
 *
 * A 409 Conflict is thrown when a user tried to make a directory over an existing
 * file or in a parent directory that doesn't exist.
 */
exports.jsDAV_Exception_Conflict = function(msg, extra) {
    this.code    = 409;
    this.type    = "jsDAV_Exception_Conflict";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_Conflict.prototype = new exports.jsDAV_Exception();

/**
 * Locked
 *
 * The 423 is thrown when a client tried to access a resource that was locked,
 * without supplying a valid lock token
 */
exports.jsDAV_Exception_Locked = function(lock) {
    this.code = 423;
    this.type = this.message = "jsDAV_Exception_Locked";
    this.lock = lock;

    this.serialize = function(handler, errorNode) {
        if (this.lock) {
            return errorNode + "<d:lock-token-submitted><d:href>" + this.lock.uri
                + "</d:href></d:lock-token-submitted>";
        }
        return errorNode;
    };
};
exports.jsDAV_Exception_Locked.prototype = new exports.jsDAV_Exception_Locked();

/**
 * ConflictingLock
 *
 * Similar to Exception_Locked, this exception thrown when a LOCK request
 * was made, on a resource which was already locked
 */
exports.jsDAV_Exception_ConflictingLock = function(lock) {
    this.type = this.message = "jsDAV_Exception_ConflictingLock";
    this.lock = lock;

    this.serialize = function(handler, errorNode) {
        if (this.lock) {
            return errorNode + "<d:no-conflicting-lock><d:href>" + this.lock.uri
                + "</d:href></d:no-conflicting-lock>";
        }
        return errorNode;
    };
};
exports.jsDAV_Exception_ConflictingLock.prototype = new exports.jsDAV_Exception_Locked();

/**
 * FileNotFound
 *
 * This Exception is thrown when a Node couldn't be found. It returns HTTP error
 * code 404
 */
exports.jsDAV_Exception_FileNotFound = function(msg, extra) {
    this.code    = 404;
    this.type    = "jsDAV_Exception_FileNotFound";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_FileNotFound.prototype = new exports.jsDAV_Exception();

/**
 * Forbidden
 *
 * This exception is thrown whenever a user tries to do an operation he's not
 * allowed to
 */
exports.jsDAV_Exception_Forbidden = function(msg, extra) {
    this.code    = 403;
    this.type    = "jsDAV_Exception_Forbidden";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_Forbidden.prototype = new exports.jsDAV_Exception();

/**
 * InsufficientStorage
 *
 * This Exception can be thrown, when for example a harddisk is full or a quota
 * is exceeded
 */
exports.jsDAV_Exception_InsufficientStorage = function(msg, extra) {
    this.code    = 507;
    this.type    = "jsDAV_Exception_InsufficientStorage";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_InsufficientStorage.prototype = new exports.jsDAV_Exception();

/**
 * InvalidResourceType
 *
 * This exception is thrown when the user tried to create a new collection, with
 * a special resourcetype value that was not recognized by the server.
 *
 * See RFC5689 section 3.3
 */
exports.jsDAV_Exception_InvalidResourceType = function(msg, extra) {
    this.type    = "jsDAV_Exception_InvalidResourceType";
    this.message = msg || this.type;

    this.serialize = function(handler, errorNode) {
        return errorNode + "<d:valid-resourcetype />";
    };
};
exports.jsDAV_Exception_InvalidResourceType.prototype = new exports.jsDAV_Exception_Forbidden();

/**
 * LockTokenMatchesRequestUri
 *
 * This exception is thrown by UNLOCK if a supplied lock-token is invalid
 */
exports.jsDAV_Exception_LockTokenMatchesRequestUri = function() {
    this.type    = "jsDAV_Exception_LockTokenMatchesRequestUri";
    this.message = "The locktoken supplied does not match any locks on this entity";

    this.serialize = function(handler, errorNode) {
        return errorNode + "<d:lock-token-matches-request-uri />";
    };
};
exports.jsDAV_Exception_LockTokenMatchesRequestUri.prototype = new exports.jsDAV_Exception_Conflict();

/**
 * MethodNotAllowed
 *
 * The 405 is thrown when a client tried to create a directory on an already existing directory
 */
exports.jsDAV_Exception_MethodNotAllowed = function(msg, extra) {
    this.code    = 405;
    this.type    = "jsDAV_Exception_MethodNotAllowed";
    this.message = msg || this.type;

    this.getHTTPHeaders = function(handler, cbmethods) {
        handler.getAllowedMethods(handler.getRequestUri(), function(err, methods) {
            cbmethods(err, {
                "Allow" : methods.join(", ").toUpperCase()
            });
        });
    };
};
exports.jsDAV_Exception_MethodNotAllowed.prototype = new exports.jsDAV_Exception();

/**
 * NotAuthenticated
 *
 * This exception is thrown when the client did not provide valid
 * authentication credentials.
 */
exports.jsDAV_Exception_NotAuthenticated = function(msg, extra) {
    this.code    = 401;
    this.type    = "jsDAV_Exception_NotAuthenticated";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_NotAuthenticated.prototype = new exports.jsDAV_Exception();

/**
 * NotImplemented
 *
 * This exception is thrown when the client tried to call an unsupported HTTP
 * method or other feature
 */
exports.jsDAV_Exception_NotImplemented = function(msg, extra) {
    this.code    = 501;
    this.type    = "jsDAV_Exception_NotImplemented";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_NotImplemented.prototype = new exports.jsDAV_Exception();

/**
 * PreconditionFailed
 *
 * This exception is normally thrown when a client submitted a conditional request,
 * like for example an If, If-None-Match or If-Match header, which caused the HTTP
 * request to not execute (the condition of the header failed)
 */
exports.jsDAV_Exception_PreconditionFailed = function(msg, extra) {
    this.code    = 412;
    this.type    = "jsDAV_Exception_PreconditionFailed";
    this.message = msg || this.type;
    this.header  = extra;

    this.serialize = function(handler, errorNode) {
        return this.header
            ? errorNode + "<a:header>" + this.header + "</a:header>"
            : errorNode;
    };
};
exports.jsDAV_Exception_PreconditionFailed.prototype = new exports.jsDAV_Exception();

/**
 * ReportNotImplemented
 *
 * This exception is thrown when the client requested an unknown report through
 * the REPORT method
 */
exports.jsDAV_Exception_ReportNotImplemented = function(msg, extra) {
    this.type    = "jsDAV_Exception_ReportNotImplemented";
    this.message = msg || this.type;

    this.serialize = function(handler, errorNode) {
        return errorNode + "<d:supported-report/>";
    };
};
exports.jsDAV_Exception_ReportNotImplemented.prototype = new exports.jsDAV_Exception_NotImplemented();

/**
 * RequestedRangeNotSatisfiable
 *
 * This exception is normally thrown when the user
 * request a range that is out of the entity bounds.
 */
exports.jsDAV_Exception_RequestedRangeNotSatisfiable = function(msg, extra) {
    this.code    = 416;
    this.type    = "jsDAV_Exception_RequestedRangeNotSatisfiable";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_RequestedRangeNotSatisfiable.prototype = new exports.jsDAV_Exception();

/**
 * UnSupportedMediaType
 *
 * The 415 Unsupported Media Type status code is generally sent back when the client
 * tried to call an HTTP method, with a body the server didn't understand
 */
exports.jsDAV_Exception_UnsupportedMediaType = function(msg, extra) {
    this.code    = 415;
    this.type    = "jsDAV_Exception_UnsupportedMediaType";
    this.message = msg || this.type;
};
exports.jsDAV_Exception_UnsupportedMediaType.prototype = new exports.jsDAV_Exception();
<|endoftext|>