<commit_before>'use strict';

var fs = require('fs-extra'),
    path = require('path'),
    exec = require('child_process').exec,
    os = require('os'),
    thisPackage = require('../package.json');

function exclude (exclusions, file) {
    return (exclusions || []).some(function (exclusion) {
        if (exclusion[0] === '*') {
            return path.extname(file) === exclusion.substring(1);
        }
        else {
            return path.basename(file) === exclusion;
        }
    });
}

function include (filetype, file) {
    return !filetype || file.indexOf(filetype, file.length - filetype.length) >= 0;
}

function forEachFileIn (dir, fileCallback, options) {
    fs.readdirSync(dir).forEach(function (file) {
        var filePath = path.join(dir, file);

        if (!exclude(options.exclude, filePath)) {
            if (fs.lstatSync(filePath).isDirectory()) {
                forEachFileIn(filePath, fileCallback, options);
            } else {
                if (include(options.filetype, filePath)) {
                    fileCallback(filePath);
                }
            }
        }
    });
}

module.exports = function (grunt) {

    grunt.registerTask('wsCheck', 'Check for whitespace problems that make diffing harder', function () {
        var errors = [],
            wsCheck = function (file) {
                var contents = fs.readFileSync(file, 'utf8'),
                    lines = contents.split(os.EOL);

                lines.forEach(function (line) {
                    var trailingWhitespaceErrors = line.match(/\s$/) || [],
                        tabErrors = line.match(/^.*\t.*$/) || [];

                    errors = errors.concat(trailingWhitespaceErrors.map(function () {
                        return file + ' has trailing whitespace\n\t<<' + line + '>>';
                    })).concat(tabErrors.map(function () {
                        return file + ' has tabs instead of spaces\n\t<<' + line + '>>';
                    }));
                });

                if (contents[contents.length-1] !== '\n') {
                    errors = errors.concat(file + ' has no trailing newline');
                }
                else if (contents[contents.length-2] === os.EOL) {
                    errors = errors.concat(file + ' has more than one trailing newline');
                }
            },
            exclusions = ['node_modules', '.git', '.DS_Store', '.idea', 'images', 'dist', 'mountebank.iml', 'mb.log', '*.pid', 'package.json'];

        forEachFileIn('.', wsCheck, { exclude: exclusions });

        if (errors.length > 0) {
            console.error(errors.join(os.EOL));
            process.exit(1);
        }
    });

    grunt.registerTask('jsCheck', 'Run JavaScript checks not covered by jshint', function () {
        var errors = [],
            jsCheck = function (file) {
                var contents = fs.readFileSync(file, 'utf8'),
                    lines = contents.split(os.EOL);

                if (contents.indexOf("'use strict'") < 0) {
                    errors = errors.concat(file + " does not start with 'use strict';");
                }
                lines.forEach(function (line) {
                    var accidentalOnlyErrors = line.match(/(describe|[Ii]t)\.only\(/) || [],
                        functionDeclarationErrors = line.match(/(function [A-Za-z]+\(|function\()/) || [];

                    errors = errors.concat(accidentalOnlyErrors.map(function () {
                        return file + ' appears to have been left with a mocha .only() call\n\t' + line;
                    })).concat(functionDeclarationErrors.map(function () {
                        return file + ' uses function xyz() instead of function xyz () style for function definitions\n\t' + line;
                    }));
                });
            },
            exclusions = ['node_modules', 'dist', 'staticAnalysis.js', 'testHelpers.js', '*.pid'];

        forEachFileIn('.', jsCheck, { exclude: exclusions, filetype: '.js' });

        if (errors.length > 0) {
            console.error(errors.join(os.EOL));
            process.exit(1);
        }
    });

    grunt.registerTask('deadCheck', 'Check for unused dependencies in package.json', function () {
        var dependencies = Object.keys(thisPackage.dependencies).concat(Object.keys(thisPackage.devDependencies)),
            usedCount = {},
            dependencyCheck = function (file) {
                var contents = fs.readFileSync(file, 'utf8');

                dependencies.forEach(function (dependency) {
                    if (contents.indexOf("require('" + dependency + "')") >= 0 ||
                        contents.indexOf("loadNpmTasks('" + dependency + "')") >= 0) {
                        usedCount[dependency] += 1;
                    }
                });
            },
            exclusions = ['node_modules', '.git', '.DS_Store', '.idea', 'images', 'dist', 'mountebank.iml', 'mb.log', '*.pid'],
            errors = [],
            whitelist = ['npm', 'grunt', 'mocha', 'mocha-lcov-reporter', 'coveralls', 'grunt-cli'];

        dependencies.forEach(function (dependency) {
            usedCount[dependency] = 0;
        });
        whitelist.forEach(function (dependency) {
            usedCount[dependency] += 1;
        });

        forEachFileIn('.', dependencyCheck, { exclude: exclusions });

        dependencies.forEach(function (dependency) {
            if (usedCount[dependency] === 0) {
                errors.push(dependency + ' is depended on in package.json but is never required');
            }
        });

        if (errors.length > 0) {
            console.error(errors.join(os.EOL));
            process.exit(1);
        }
    });

    grunt.registerTask('coveralls', 'Send coverage output to coveralls.io', function () {
        var done = this.async(),
            mocha = './node_modules/.bin/mocha --require blanket --reporter mocha-lcov-reporter test/**/*.js',
            command = mocha + ' | ./node_modules/coveralls/bin/coveralls.js';

        exec(command, function (error, stdout, stderr) {
            if (stdout) { console.log(stdout); }
            if (stderr) { console.log(stderr); }
            if (error) { throw error; }
            done();
        });
    });
};
<commit_msg>Changing to be more portable to Windows / different treating of \s character class with different newlines<commit_after>'use strict';

var fs = require('fs-extra'),
    path = require('path'),
    exec = require('child_process').exec,
    os = require('os'),
    thisPackage = require('../package.json');

function exclude (exclusions, file) {
    return (exclusions || []).some(function (exclusion) {
        if (exclusion[0] === '*') {
            return path.extname(file) === exclusion.substring(1);
        }
        else {
            return path.basename(file) === exclusion;
        }
    });
}

function include (filetype, file) {
    return !filetype || file.indexOf(filetype, file.length - filetype.length) >= 0;
}

function forEachFileIn (dir, fileCallback, options) {
    fs.readdirSync(dir).forEach(function (file) {
        var filePath = path.join(dir, file);

        if (!exclude(options.exclude, filePath)) {
            if (fs.lstatSync(filePath).isDirectory()) {
                forEachFileIn(filePath, fileCallback, options);
            } else {
                if (include(options.filetype, filePath)) {
                    fileCallback(filePath);
                }
            }
        }
    });
}

module.exports = function (grunt) {

    grunt.registerTask('wsCheck', 'Check for whitespace problems that make diffing harder', function () {
        var errors = [],
            wsCheck = function (file) {
                var contents = fs.readFileSync(file, 'utf8'),
                    lines = contents.split(os.EOL);

                lines.forEach(function (line) {
                    var trailingWhitespaceErrors = line.match(/ $/) || [],
                        tabErrors = line.match(/^.*\t.*$/) || [];

                    errors = errors.concat(trailingWhitespaceErrors.map(function () {
                        return file + ' has trailing whitespace\n\t<<' + line + '>>';
                    })).concat(tabErrors.map(function () {
                        return file + ' has tabs instead of spaces\n\t<<' + line + '>>';
                    }));
                });

                if (contents[contents.length-1] !== '\n') {
                    errors = errors.concat(file + ' has no trailing newline');
                }
                else if (contents[contents.length-2] === os.EOL) {
                    errors = errors.concat(file + ' has more than one trailing newline');
                }
            },
            exclusions = ['node_modules', '.git', '.DS_Store', '.idea', 'images', 'dist', 'mountebank.iml', 'mb.log', '*.pid', 'package.json'];

        forEachFileIn('.', wsCheck, { exclude: exclusions });

        if (errors.length > 0) {
            console.error(errors.join(os.EOL));
            process.exit(1);
        }
    });

    grunt.registerTask('jsCheck', 'Run JavaScript checks not covered by jshint', function () {
        var errors = [],
            jsCheck = function (file) {
                var contents = fs.readFileSync(file, 'utf8'),
                    lines = contents.split(os.EOL);

                if (contents.indexOf("'use strict'") < 0) {
                    errors = errors.concat(file + " does not start with 'use strict';");
                }
                lines.forEach(function (line) {
                    var accidentalOnlyErrors = line.match(/(describe|[Ii]t)\.only\(/) || [],
                        functionDeclarationErrors = line.match(/(function [A-Za-z]+\(|function\()/) || [];

                    errors = errors.concat(accidentalOnlyErrors.map(function () {
                        return file + ' appears to have been left with a mocha .only() call\n\t' + line;
                    })).concat(functionDeclarationErrors.map(function () {
                        return file + ' uses function xyz() instead of function xyz () style for function definitions\n\t' + line;
                    }));
                });
            },
            exclusions = ['node_modules', 'dist', 'staticAnalysis.js', 'testHelpers.js', '*.pid'];

        forEachFileIn('.', jsCheck, { exclude: exclusions, filetype: '.js' });

        if (errors.length > 0) {
            console.error(errors.join(os.EOL));
            process.exit(1);
        }
    });

    grunt.registerTask('deadCheck', 'Check for unused dependencies in package.json', function () {
        var dependencies = Object.keys(thisPackage.dependencies).concat(Object.keys(thisPackage.devDependencies)),
            usedCount = {},
            dependencyCheck = function (file) {
                var contents = fs.readFileSync(file, 'utf8');

                dependencies.forEach(function (dependency) {
                    if (contents.indexOf("require('" + dependency + "')") >= 0 ||
                        contents.indexOf("loadNpmTasks('" + dependency + "')") >= 0) {
                        usedCount[dependency] += 1;
                    }
                });
            },
            exclusions = ['node_modules', '.git', '.DS_Store', '.idea', 'images', 'dist', 'mountebank.iml', 'mb.log', '*.pid'],
            errors = [],
            whitelist = ['npm', 'grunt', 'mocha', 'mocha-lcov-reporter', 'coveralls', 'grunt-cli'];

        dependencies.forEach(function (dependency) {
            usedCount[dependency] = 0;
        });
        whitelist.forEach(function (dependency) {
            usedCount[dependency] += 1;
        });

        forEachFileIn('.', dependencyCheck, { exclude: exclusions });

        dependencies.forEach(function (dependency) {
            if (usedCount[dependency] === 0) {
                errors.push(dependency + ' is depended on in package.json but is never required');
            }
        });

        if (errors.length > 0) {
            console.error(errors.join(os.EOL));
            process.exit(1);
        }
    });

    grunt.registerTask('coveralls', 'Send coverage output to coveralls.io', function () {
        var done = this.async(),
            mocha = './node_modules/.bin/mocha --require blanket --reporter mocha-lcov-reporter test/**/*.js',
            command = mocha + ' | ./node_modules/coveralls/bin/coveralls.js';

        exec(command, function (error, stdout, stderr) {
            if (stdout) { console.log(stdout); }
            if (stderr) { console.log(stderr); }
            if (error) { throw error; }
            done();
        });
    });
};
<|endoftext|>


<commit_before>'use strict';

import Reflux from 'reflux';
import SocketClient from '../utils/ServiceProviderSocketClient.js';

const event = SocketClient('getOpenSearchWork');

let WorkActions = Reflux.createAction({
  children: ['updated', 'failed']
});

WorkActions.listen((res) => {
  console.log(res);
  if (res.id.length > 0) {
    event.request({pid: res.id, offset: 1, worksPerPage: 1, allManifestations: true});
  }
  else {
    WorkActions.updated([]);
  }
});

event.response(WorkActions.updated);

export default WorkActions;
<commit_msg>remove log<commit_after>'use strict';

import Reflux from 'reflux';
import SocketClient from '../utils/ServiceProviderSocketClient.js';

const event = SocketClient('getOpenSearchWork');

let WorkActions = Reflux.createAction({
  children: ['updated', 'failed']
});

WorkActions.listen((res) => {
  if (res.id.length > 0) {
    event.request({pid: res.id, offset: 1, worksPerPage: 1, allManifestations: true});
  }
  else {
    WorkActions.updated([]);
  }
});

event.response(WorkActions.updated);

export default WorkActions;
<|endoftext|>


<commit_before>import Ember from 'ember';
import Subscriptions from 'ember-cable/core/subscriptions';
import Connection from 'ember-cable/core/connection';

export default Ember.Object.extend({
  url: null,

  setupConnection: Ember.on('init', function() {
    this.set('subscriptions', Subscriptions.create({ consumer: this }));
    this.set('connection', Connection.create({ consumer: this }));
  }),

  send(data) {
    this.get('connection').send(data);
  }
});
<commit_msg>added simple queue to consumer<commit_after>import Ember from 'ember';
import Subscriptions from 'ember-cable/core/subscriptions';
import Connection from 'ember-cable/core/connection';

export default Ember.Object.extend({
  url: null,
  queue: null,

  setupConnection: Ember.on('init', function() {
    this.set('subscriptions', Subscriptions.create({ consumer: this }));
    this.set('connection', Connection.create({ consumer: this }));
    this.set('queue', []);
  }),

  send(data) {
    this.get('queue').push(data);
  },

  queueObserver: Ember.on('init', Ember.observer('queue.[]', 'connection.connected', function() {
    const connection = this.get('connection');
    const queue = this.get('queue');

    if (!connection || !connection.get('connected')) {
      return;
    }

    if (Ember.isEmpty(queue)) {
      return;
    }

    queue.forEach((data, index) => {
      connection.send(data);
      queue.splice(index, 1);
    });
  }))
});
<|endoftext|>


<commit_before>'use strict';

const eventTypes = require('./resources/trackables.json').eventTypes;
const rewardTypes = require('./resources/trackables.json').rewardTypes;

const fissures = ['fissures.t1', 'fissures.t2', 'fissures.t3', 'fissures.t4'];
const syndicates = ['syndicate.arbiters', 'syndicate.suda', 'syndicate.loka', 'syndicate.perrin', 'syndicate.veil', 'syndicate.meridian'];
const conclave = ['conclave.weeklies', 'conclave.dailies'];
const deals = ['deals.featured', 'deals.popular']

function trackablesFromParameters (paramString) {
  let items = paramString.split(' ');

  const trackables = {
    events: [],
    items: [],
  }

  if (items[0].toLowerCase() === 'all') {
    trackables.events = trackables.events.concat(eventTypes);
    trackables.items = trackables.items.concat(rewardTypes);
  } else {
    items = items.map(item => item.trim());
    items.forEach((item) => {
      const i = item.toLowerCase().trim();
      if (i === 'items') {
        trackables.items = trackables.items.concat(rewardTypes);
      } else if (i === 'events') {
        trackables.events = trackables.events.concat(eventTypes);
      } else if (i === 'fissures') {
        trackables.events = trackables.events.concat(fissures);
      } else if (i === 'syndicates') {
        trackables.events = trackables.events.concat(syndicates);
      } else if (i === 'conclave') {
        trackables.events = trackables.events.concat(conclave);
      } else if (i === 'deals') {
        trackables.events = trackables.events.concat(deals);
      } else if (rewardTypes.includes(item.trim())) {
        trackables.items.push(item.trim());
      } else if (eventTypes.includes(item.trim())) {
        trackables.events.push(item.trim());
      }
    });
  }
  return trackables;
}

function getTrackInstructionEmbed(message, prefix, call) {
  const embed = {
    type: 'rich',
    color: 0x0000ff,
    fields: [
      {
        name: `${prefix}${call} <event(s)/item(s) to ${call === 'untrack' ? 'un' : ''}track>`,
        value: 'Track events/items to be alerted in this channel.',
        inline: true,
      },
      {
        name: 'Possible values:',
        value: '_ _',
        inline: false,
      },
      {
        name: '**Events:**',
        value: `${eventTypes.concat(['all', 'events', 'fissures', 'syndicates', 'conclave']).join('\n')}`,
        inline: true,
      },
      {
        name: '**Rewards:**',
        value: `${rewardTypes.join('\n')}\nall\nitems`,
        inline: true,
      },
    ],
  };

  switch (call) {
    case 'track':
      embed.fields[0].value = 'Track events/items to be alerted in this channel.'
      break;
    case 'untrack':
      embed.fields[0].value = 'Untrack events/items to be alerted in this channel.'
      break;
    case 'set ping':
      embed.fields[0].value = 'Set the text added before an event/item notification.'
      embed.fields.push({
        name: '**Ping:**',
        value: 'Whatever string you want to be added before a notification for this item or event. If you leave this blank, the ping for this item/event will be cleared',
        inline: true,
      });
      break;
  }
  return embed;
}

module.exports = { trackablesFromParameters, getTrackInstructionEmbed };
<commit_msg>Update TrackFunctions.js<commit_after>'use strict';

const eventTypes = require('./resources/trackables.json').eventTypes;
const rewardTypes = require('./resources/trackables.json').rewardTypes;

const fissures = ['fissures.t1', 'fissures.t2', 'fissures.t3', 'fissures.t4'];
const syndicates = ['syndicate.arbiters', 'syndicate.suda', 'syndicate.loka', 'syndicate.perrin', 'syndicate.veil', 'syndicate.meridian'];
const conclave = ['conclave.weeklies', 'conclave.dailies'];
const deals = ['deals.featured', 'deals.popular']

function trackablesFromParameters (paramString) {
  let items = paramString.split(' ');

  const trackables = {
    events: [],
    items: [],
  }

  if (items[0].toLowerCase() === 'all') {
    trackables.events = trackables.events.concat(eventTypes);
    trackables.items = trackables.items.concat(rewardTypes);
  } else {
    items = items.map(item => item.trim());
    items.forEach((item) => {
      const i = item.toLowerCase().trim();
      if (i === 'items') {
        trackables.items = trackables.items.concat(rewardTypes);
      } else if (i === 'events') {
        trackables.events = trackables.events.concat(eventTypes);
      } else if (i === 'fissures') {
        trackables.events = trackables.events.concat(fissures);
      } else if (i === 'syndicates') {
        trackables.events = trackables.events.concat(syndicates);
      } else if (i === 'conclave') {
        trackables.events = trackables.events.concat(conclave);
      } else if (i === 'deals') {
        trackables.events = trackables.events.concat(deals);
      } else if (rewardTypes.includes(item.trim())) {
        trackables.items.push(item.trim());
      } else if (eventTypes.includes(item.trim())) {
        trackables.events.push(item.trim());
      }
    });
  }
  console.log(`Events: ${trackables.events.length}, Items: ${trackables.items.length}`);
  return trackables;
}

function getTrackInstructionEmbed(message, prefix, call) {
  const embed = {
    type: 'rich',
    color: 0x0000ff,
    fields: [
      {
        name: `${prefix}${call} <event(s)/item(s) to ${call === 'untrack' ? 'un' : ''}track>`,
        value: 'Track events/items to be alerted in this channel.',
        inline: true,
      },
      {
        name: 'Possible values:',
        value: '_ _',
        inline: false,
      },
      {
        name: '**Events:**',
        value: `${eventTypes.concat(['all', 'events', 'fissures', 'syndicates', 'conclave']).join('\n')}`,
        inline: true,
      },
      {
        name: '**Rewards:**',
        value: `${rewardTypes.join('\n')}\nall\nitems`,
        inline: true,
      },
    ],
  };

  switch (call) {
    case 'track':
      embed.fields[0].value = 'Track events/items to be alerted in this channel.'
      break;
    case 'untrack':
      embed.fields[0].value = 'Untrack events/items to be alerted in this channel.'
      break;
    case 'set ping':
      embed.fields[0].value = 'Set the text added before an event/item notification.'
      embed.fields.push({
        name: '**Ping:**',
        value: 'Whatever string you want to be added before a notification for this item or event. If you leave this blank, the ping for this item/event will be cleared',
        inline: true,
      });
      break;
  }
  return embed;
}

module.exports = { trackablesFromParameters, getTrackInstructionEmbed };
<|endoftext|>


<commit_before>#region Copyright
//
// DotNetNuke® - http://www.dotnetnuke.com
// Copyright (c) 2002-2017
// by DotNetNuke Corporation
//
// 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.
#endregion

define(['jquery', 'knockout'], function ($, ko) {
    'use strict';

    var changeSort = function (e) {
        var $element = $(e.target);
        var viewModel = ko.dataFor($element.parent()[0]);
        var column = ko.dataFor($element[0]);

        if (viewModel.sortColumn() !== column) {
            viewModel.sortType(0);

            $element.parent().find('.table-sortable.current').removeClass('current');
        }

        viewModel.sortColumn(column);

        var sortType = viewModel.sortType();
        switch (sortType) {
            case 0:
                viewModel.sortType(1);
                break;
            case 1:
                viewModel.sortType(2);
                break;
            case 2:
                viewModel.sortType(0);
                break;
            default:
                viewModel.sortType(1);
                break;
        }

        sortType = viewModel.sortType();

        viewModel.currentPage(1);
        $element.addClass('current');
        $element.find('span.sort').removeClass('asc desc');
        if (sortType === 1 || sortType === 2) {
            $element.find('span.sort').addClass(sortType === 1 ? 'asc' : 'desc');
        }
    }

    var init = function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        var $element = $(element);

        $element.addClass('table-sortable')
            .append('')
            .click(changeSort);
    }

    var update = function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    }

    ko.bindingHandlers.sort = {
        init: init,
        update: update
    };
});<commit_msg>CONTENT-8120: Comment the Region declaration in SqlConsole sort javascript file<commit_after>//#region Copyright
//
// DotNetNuke® - http://www.dotnetnuke.com
// Copyright (c) 2002-2017
// by DotNetNuke Corporation
//
// 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.
//#endregion

define(['jquery', 'knockout'], function ($, ko) {
    'use strict';

    var changeSort = function (e) {
        var $element = $(e.target);
        var viewModel = ko.dataFor($element.parent()[0]);
        var column = ko.dataFor($element[0]);

        if (viewModel.sortColumn() !== column) {
            viewModel.sortType(0);

            $element.parent().find('.table-sortable.current').removeClass('current');
        }

        viewModel.sortColumn(column);

        var sortType = viewModel.sortType();
        switch (sortType) {
            case 0:
                viewModel.sortType(1);
                break;
            case 1:
                viewModel.sortType(2);
                break;
            case 2:
                viewModel.sortType(0);
                break;
            default:
                viewModel.sortType(1);
                break;
        }

        sortType = viewModel.sortType();

        viewModel.currentPage(1);
        $element.addClass('current');
        $element.find('span.sort').removeClass('asc desc');
        if (sortType === 1 || sortType === 2) {
            $element.find('span.sort').addClass(sortType === 1 ? 'asc' : 'desc');
        }
    }

    var init = function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        var $element = $(element);

        $element.addClass('table-sortable')
            .append('')
            .click(changeSort);
    }

    var update = function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    }

    ko.bindingHandlers.sort = {
        init: init,
        update: update
    };
});<|endoftext|>


<commit_before>(function() {

  var _slice = Array.prototype.slice;

  /**
   * Takes id and returns an element with that id (if one exists in a document)
   * @memberOf fabric.util
   * @param {String|HTMLElement} id
   * @return {HTMLElement|null}
   */
  function getById(id) {
    return typeof id === 'string' ? fabric.document.getElementById(id) : id;
  }

  /**
   * Converts an array-like object (e.g. arguments or NodeList) to an array
   * @memberOf fabric.util
   * @param {Object} arrayLike
   * @return {Array}
   */
  var toArray = function(arrayLike) {
    return _slice.call(arrayLike, 0);
  };

  var sliceCanConvertNodelists;
  try {
    sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array;
  }
  catch(err) { }

  if (!sliceCanConvertNodelists) {
    toArray = function(arrayLike) {
      var arr = new Array(arrayLike.length), i = arrayLike.length;
      while (i--) {
        arr[i] = arrayLike[i];
      }
      return arr;
    };
  }

  /**
   * Creates specified element with specified attributes
   * @memberOf fabric.util
   * @param {String} tagName Type of an element to create
   * @param {Object} [attributes] Attributes to set on an element
   * @return {HTMLElement} Newly created element
   */
  function makeElement(tagName, attributes) {
    var el = fabric.document.createElement(tagName);
    for (var prop in attributes) {
      if (prop === 'class') {
        el.className = attributes[prop];
      }
      else if (prop === 'for') {
        el.htmlFor = attributes[prop];
      }
      else {
        el.setAttribute(prop, attributes[prop]);
      }
    }
    return el;
  }

  /**
   * Adds class to an element
   * @memberOf fabric.util
   * @param {HTMLElement} element Element to add class to
   * @param {String} className Class to add to an element
   */
  function addClass(element, className) {
    if ((' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) {
      element.className += (element.className ? ' ' : '') + className;
    }
  }

  /**
   * Wraps element with another element
   * @memberOf fabric.util
   * @param {HTMLElement} element Element to wrap
   * @param {HTMLElement|String} wrapper Element to wrap with
   * @param {Object} [attributes] Attributes to set on a wrapper
   * @return {HTMLElement} wrapper
   */
  function wrapElement(element, wrapper, attributes) {
    if (typeof wrapper === 'string') {
      wrapper = makeElement(wrapper, attributes);
    }
    if (element.parentNode) {
      element.parentNode.replaceChild(wrapper, element);
    }
    wrapper.appendChild(element);
    return wrapper;
  }

  /**
   * Returns element scroll offsets
   * @memberOf fabric.util
   * @param {HTMLElement} element Element to operate on
   * @param {HTMLElement} upperCanvasEl Upper canvas element
   * @return {Object} Object with left/top values
   */
  function getScrollLeftTop(element, upperCanvasEl) {

    var firstFixedAncestor,
        origElement,
        left = 0,
        top = 0,
        docElement = fabric.document.documentElement,
        body = fabric.document.body || {
          scrollLeft: 0, scrollTop: 0
        };

    origElement = element;

    while (element && element.parentNode && !firstFixedAncestor) {

      element = element.parentNode;

      if (element !== fabric.document &&
          fabric.util.getElementStyle(element, 'position') === 'fixed') {
        firstFixedAncestor = element;
      }

      if (element !== fabric.document &&
          origElement !== upperCanvasEl &&
          fabric.util.getElementStyle(element, 'position') === 'absolute') {
        left = 0;
        top = 0;
      }
      else if (element === fabric.document) {
        left = body.scrollLeft || docElement.scrollLeft || 0;
        top = body.scrollTop ||  docElement.scrollTop || 0;
      }
      else {
        left += element.scrollLeft || 0;
        top += element.scrollTop || 0;
      }
    }

    return { left: left, top: top };
  }

  /**
   * Returns offset for a given element
   * @function
   * @memberOf fabric.util
   * @param {HTMLElement} element Element to get offset for
   * @return {Object} Object with "left" and "top" properties
   */
  function getElementOffset(element) {
    var docElem,
        box = {left: 0, top: 0},
        doc = element && element.ownerDocument,
        offset = {left: 0, top: 0},
        scrollLeftTop,
        offsetAttributes = {
          'borderLeftWidth': 'left',
          'borderTopWidth':  'top',
          'paddingLeft':     'left',
          'paddingTop':      'top'
        };

    if (!doc){
      return {left: 0, top: 0};
    }

    for (var attr in offsetAttributes) {
      offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0;
    }

    docElem = doc.documentElement;
    if ( typeof element.getBoundingClientRect !== "undefined" ) {
      box = element.getBoundingClientRect();
    }

    scrollLeftTop = fabric.util.getScrollLeftTop(element, null);

    return {
      left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left,
      top: box.top + scrollLeftTop.top - (docElem.clientTop || 0)  + offset.top
    };
  }

  /**
  * Returns style attribute value of a given element
  * @memberOf fabric.util
  * @param {HTMLElement} element Element to get style attribute for
  * @param {String} attr Style attribute to get for element
  * @return {String} Style attribute value of the given element.
  */
  function getElementStyle(element, attr) {
    if (!element.style) {
      element.style = { };
    }

    if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) {
      return fabric.document.defaultView.getComputedStyle(element, null)[attr];
    }
    else {
      var value = element.style[attr];
      if (!value && element.currentStyle) {
        value = element.currentStyle[attr];
      }
      return value;
    }
  }

  (function () {
    var style = fabric.document.documentElement.style;

    var selectProp = 'userSelect' in style
      ? 'userSelect'
      : 'MozUserSelect' in style
        ? 'MozUserSelect'
        : 'WebkitUserSelect' in style
          ? 'WebkitUserSelect'
          : 'KhtmlUserSelect' in style
            ? 'KhtmlUserSelect'
            : '';

    /**
     * Makes element unselectable
     * @memberOf fabric.util
     * @param {HTMLElement} element Element to make unselectable
     * @return {HTMLElement} Element that was passed in
     */
    function makeElementUnselectable(element) {
      if (typeof element.onselectstart !== 'undefined') {
        element.onselectstart = fabric.util.falseFunction;
      }
      if (selectProp) {
        element.style[selectProp] = 'none';
      }
      else if (typeof element.unselectable === 'string') {
        element.unselectable = 'on';
      }
      return element;
    }

    /**
     * Makes element selectable
     * @memberOf fabric.util
     * @param {HTMLElement} element Element to make selectable
     * @return {HTMLElement} Element that was passed in
     */
    function makeElementSelectable(element) {
      if (typeof element.onselectstart !== 'undefined') {
        element.onselectstart = null;
      }
      if (selectProp) {
        element.style[selectProp] = '';
      }
      else if (typeof element.unselectable === 'string') {
        element.unselectable = '';
      }
      return element;
    }

    fabric.util.makeElementUnselectable = makeElementUnselectable;
    fabric.util.makeElementSelectable = makeElementSelectable;
  })();

  (function() {

    /**
     * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading
     * @memberOf fabric.util
     * @param {String} url URL of a script to load
     * @param {Function} callback Callback to execute when script is finished loading
     */
    function getScript(url, callback) {
      var headEl = fabric.document.getElementsByTagName("head")[0],
          scriptEl = fabric.document.createElement('script'),
          loading = true;

      /** @ignore */
      scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) {
        if (loading) {
          if (typeof this.readyState === 'string' &&
              this.readyState !== 'loaded' &&
              this.readyState !== 'complete') return;
          loading = false;
          callback(e || fabric.window.event);
          scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;
        }
      };
      scriptEl.src = url;
      headEl.appendChild(scriptEl);
      // causes issue in Opera
      // headEl.removeChild(scriptEl);
    }

    fabric.util.getScript = getScript;
  })();

  fabric.util.getById = getById;
  fabric.util.toArray = toArray;
  fabric.util.makeElement = makeElement;
  fabric.util.addClass = addClass;
  fabric.util.wrapElement = wrapElement;
  fabric.util.getScrollLeftTop = getScrollLeftTop;
  fabric.util.getElementOffset = getElementOffset;
  fabric.util.getElementStyle = getElementStyle;

})();
<commit_msg>Fix mouse handling inside a scrollable div.<commit_after>(function() {

  var _slice = Array.prototype.slice;

  /**
   * Takes id and returns an element with that id (if one exists in a document)
   * @memberOf fabric.util
   * @param {String|HTMLElement} id
   * @return {HTMLElement|null}
   */
  function getById(id) {
    return typeof id === 'string' ? fabric.document.getElementById(id) : id;
  }

  /**
   * Converts an array-like object (e.g. arguments or NodeList) to an array
   * @memberOf fabric.util
   * @param {Object} arrayLike
   * @return {Array}
   */
  var toArray = function(arrayLike) {
    return _slice.call(arrayLike, 0);
  };

  var sliceCanConvertNodelists;
  try {
    sliceCanConvertNodelists = toArray(fabric.document.childNodes) instanceof Array;
  }
  catch(err) { }

  if (!sliceCanConvertNodelists) {
    toArray = function(arrayLike) {
      var arr = new Array(arrayLike.length), i = arrayLike.length;
      while (i--) {
        arr[i] = arrayLike[i];
      }
      return arr;
    };
  }

  /**
   * Creates specified element with specified attributes
   * @memberOf fabric.util
   * @param {String} tagName Type of an element to create
   * @param {Object} [attributes] Attributes to set on an element
   * @return {HTMLElement} Newly created element
   */
  function makeElement(tagName, attributes) {
    var el = fabric.document.createElement(tagName);
    for (var prop in attributes) {
      if (prop === 'class') {
        el.className = attributes[prop];
      }
      else if (prop === 'for') {
        el.htmlFor = attributes[prop];
      }
      else {
        el.setAttribute(prop, attributes[prop]);
      }
    }
    return el;
  }

  /**
   * Adds class to an element
   * @memberOf fabric.util
   * @param {HTMLElement} element Element to add class to
   * @param {String} className Class to add to an element
   */
  function addClass(element, className) {
    if ((' ' + element.className + ' ').indexOf(' ' + className + ' ') === -1) {
      element.className += (element.className ? ' ' : '') + className;
    }
  }

  /**
   * Wraps element with another element
   * @memberOf fabric.util
   * @param {HTMLElement} element Element to wrap
   * @param {HTMLElement|String} wrapper Element to wrap with
   * @param {Object} [attributes] Attributes to set on a wrapper
   * @return {HTMLElement} wrapper
   */
  function wrapElement(element, wrapper, attributes) {
    if (typeof wrapper === 'string') {
      wrapper = makeElement(wrapper, attributes);
    }
    if (element.parentNode) {
      element.parentNode.replaceChild(wrapper, element);
    }
    wrapper.appendChild(element);
    return wrapper;
  }

  /**
   * Returns element scroll offsets
   * @memberOf fabric.util
   * @param {HTMLElement} element Element to operate on
   * @param {HTMLElement} upperCanvasEl Upper canvas element
   * @return {Object} Object with left/top values
   */
  function getScrollLeftTop(element, upperCanvasEl) {

    var firstFixedAncestor,
        origElement,
        left = 0,
        top = 0,
        docElement = fabric.document.documentElement,
        body = fabric.document.body || {
          scrollLeft: 0, scrollTop: 0
        };

    origElement = element;

    while (element && element.parentNode && !firstFixedAncestor) {

      element = element.parentNode;

      if (element !== fabric.document &&
          fabric.util.getElementStyle(element, 'position') === 'fixed') {
        firstFixedAncestor = element;
      }

      if (element !== fabric.document &&
          origElement !== upperCanvasEl &&
          fabric.util.getElementStyle(element, 'position') === 'absolute') {
        left = 0;
        top = 0;
      }
      else if (element === fabric.document) {
        left += body.scrollLeft || docElement.scrollLeft || 0;
        top += body.scrollTop ||  docElement.scrollTop || 0;
      }
      else {
        left += element.scrollLeft || 0;
        top += element.scrollTop || 0;
      }
    }

    return { left: left, top: top };
  }

  /**
   * Returns offset for a given element
   * @function
   * @memberOf fabric.util
   * @param {HTMLElement} element Element to get offset for
   * @return {Object} Object with "left" and "top" properties
   */
  function getElementOffset(element) {
    var docElem,
        box = {left: 0, top: 0},
        doc = element && element.ownerDocument,
        offset = {left: 0, top: 0},
        scrollLeftTop,
        offsetAttributes = {
          'borderLeftWidth': 'left',
          'borderTopWidth':  'top',
          'paddingLeft':     'left',
          'paddingTop':      'top'
        };

    if (!doc){
      return {left: 0, top: 0};
    }

    for (var attr in offsetAttributes) {
      offset[offsetAttributes[attr]] += parseInt(getElementStyle(element, attr), 10) || 0;
    }

    docElem = doc.documentElement;
    if ( typeof element.getBoundingClientRect !== "undefined" ) {
      box = element.getBoundingClientRect();
    }

    scrollLeftTop = fabric.util.getScrollLeftTop(element, null);

    return {
      left: box.left + scrollLeftTop.left - (docElem.clientLeft || 0) + offset.left,
      top: box.top + scrollLeftTop.top - (docElem.clientTop || 0)  + offset.top
    };
  }

  /**
  * Returns style attribute value of a given element
  * @memberOf fabric.util
  * @param {HTMLElement} element Element to get style attribute for
  * @param {String} attr Style attribute to get for element
  * @return {String} Style attribute value of the given element.
  */
  function getElementStyle(element, attr) {
    if (!element.style) {
      element.style = { };
    }

    if (fabric.document.defaultView && fabric.document.defaultView.getComputedStyle) {
      return fabric.document.defaultView.getComputedStyle(element, null)[attr];
    }
    else {
      var value = element.style[attr];
      if (!value && element.currentStyle) {
        value = element.currentStyle[attr];
      }
      return value;
    }
  }

  (function () {
    var style = fabric.document.documentElement.style;

    var selectProp = 'userSelect' in style
      ? 'userSelect'
      : 'MozUserSelect' in style
        ? 'MozUserSelect'
        : 'WebkitUserSelect' in style
          ? 'WebkitUserSelect'
          : 'KhtmlUserSelect' in style
            ? 'KhtmlUserSelect'
            : '';

    /**
     * Makes element unselectable
     * @memberOf fabric.util
     * @param {HTMLElement} element Element to make unselectable
     * @return {HTMLElement} Element that was passed in
     */
    function makeElementUnselectable(element) {
      if (typeof element.onselectstart !== 'undefined') {
        element.onselectstart = fabric.util.falseFunction;
      }
      if (selectProp) {
        element.style[selectProp] = 'none';
      }
      else if (typeof element.unselectable === 'string') {
        element.unselectable = 'on';
      }
      return element;
    }

    /**
     * Makes element selectable
     * @memberOf fabric.util
     * @param {HTMLElement} element Element to make selectable
     * @return {HTMLElement} Element that was passed in
     */
    function makeElementSelectable(element) {
      if (typeof element.onselectstart !== 'undefined') {
        element.onselectstart = null;
      }
      if (selectProp) {
        element.style[selectProp] = '';
      }
      else if (typeof element.unselectable === 'string') {
        element.unselectable = '';
      }
      return element;
    }

    fabric.util.makeElementUnselectable = makeElementUnselectable;
    fabric.util.makeElementSelectable = makeElementSelectable;
  })();

  (function() {

    /**
     * Inserts a script element with a given url into a document; invokes callback, when that script is finished loading
     * @memberOf fabric.util
     * @param {String} url URL of a script to load
     * @param {Function} callback Callback to execute when script is finished loading
     */
    function getScript(url, callback) {
      var headEl = fabric.document.getElementsByTagName("head")[0],
          scriptEl = fabric.document.createElement('script'),
          loading = true;

      /** @ignore */
      scriptEl.onload = /** @ignore */ scriptEl.onreadystatechange = function(e) {
        if (loading) {
          if (typeof this.readyState === 'string' &&
              this.readyState !== 'loaded' &&
              this.readyState !== 'complete') return;
          loading = false;
          callback(e || fabric.window.event);
          scriptEl = scriptEl.onload = scriptEl.onreadystatechange = null;
        }
      };
      scriptEl.src = url;
      headEl.appendChild(scriptEl);
      // causes issue in Opera
      // headEl.removeChild(scriptEl);
    }

    fabric.util.getScript = getScript;
  })();

  fabric.util.getById = getById;
  fabric.util.toArray = toArray;
  fabric.util.makeElement = makeElement;
  fabric.util.addClass = addClass;
  fabric.util.wrapElement = wrapElement;
  fabric.util.getScrollLeftTop = getScrollLeftTop;
  fabric.util.getElementOffset = getElementOffset;
  fabric.util.getElementStyle = getElementStyle;

})();
<|endoftext|>


<commit_before>/*    Copyright 2020 Firewalla Inc
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

const log = require("../net2/logger.js")(__filename);
const rclient = require('../util/redis_manager.js').getRedisClient();
const sem = require('../sensor/SensorEventManager.js').getInstance();
const Block = require('./Block.js');
const DNSTool = require('../net2/DNSTool.js')
const dnsTool = new DNSTool()
const IntelTool = require('../net2/IntelTool');
const intelTool = new IntelTool();
const _ = require('lodash');
const fc = require('../net2/config.js');
const featureName = 'smart_block';
let instance = null;
const expiring = 24 * 60 * 60 * 7;  // seven days

class BlockManager {
    constructor() {
        if (instance == null) {
            instance = this;
            const refreshInterval = 15 * 60; // 15 mins
            sem.once('IPTABLES_READY', async () => {
                if (fc.isFeatureOn(featureName)) {
                    this.scheduleId = setInterval(() => {
                        this.scheduleRefreshBlockLevel();
                    }, refreshInterval * 1000)
                }
                fc.onFeature(featureName, async (feature, status) => {
                    if (feature !== featureName) {
                        return
                    }
                    await this.reenforcePolicies();
                    if (status) {
                        this.scheduleId = setInterval(() => {
                            this.scheduleRefreshBlockLevel();
                        }, refreshInterval * 1000)
                    } else {
                        clearInterval(this.scheduleId);
                    }
                })
            })
        }
        return instance
    }
    async reenforcePolicies() {
        const PolicyManager2 = require('../alarm/PolicyManager2.js');
        const pm2 = new PolicyManager2();
        log.info('smart block enabled, re-enforce policies');
        const policies = await pm2.loadActivePoliciesAsync();
        policies.map((policy) => {
            pm2.tryPolicyEnforcement(policy, 'reenforce', policy);
        })
    }
    ipBlockInfoKey(ip) {
        return `ip:block:info:${ip}`
    }
    domainBlockInfoKey(domain) {
        return `domain:block:info:${domain}`
    }
    categoryDomainBlockInfoKey(domain) {
        return `category:block:info:${domain}`
    }
    getCategoryIpMapping(category) {
        return `rdns:category:${category}`
    }
    async getPureCategoryIps(category, categoryIps, originDomain) {
        if (!fc.isFeatureOn(featureName)) {
            return categoryIps;
        }
        const pureCategoryIps = [], mixupCategoryIps = [], mixupIpInfos = [];
        try {
            const now = new Date();
            for (const categoryIp of categoryIps) {
                let pure = true;
                let mixupDomain;
                let mixupCategory;
                const domains = await dnsTool.getAllDns(categoryIp);
                for (const domain of domains) {
                    mixupDomain = domain;
                    const ips = await dnsTool.getIPsByDomain(domain);
                    for (const ip of ips) {
                        const intel = (await intelTool.getIntel(ip)) || {};
                        mixupCategory = intel.category;
                        pure = pure && intel.category == category;
                        if (!pure) break;
                    }
                    if (!pure) break;
                }
                if (pure) {
                    pureCategoryIps.push(categoryIp)
                } else {
                    mixupCategoryIps.push(categoryIp);
                    mixupIpInfos.push({
                        ip: categoryIp,
                        mixupDomain: mixupDomain,
                        mixupCategory: mixupCategory
                    })
                }
            }
            const categoryIpMappingKey = this.getCategoryIpMapping(category);
            mixupCategoryIps.length > 0 && await rclient.sremAsync(categoryIpMappingKey, mixupCategoryIps);
            pureCategoryIps.length > 0 && await rclient.saddAsync(categoryIpMappingKey, pureCategoryIps);
            const categoryDomainBlockInfoKey = this.categoryDomainBlockInfoKey(originDomain);
            await rclient.setAsync(categoryDomainBlockInfoKey, JSON.stringify({
                pureCategoryIps: pureCategoryIps,
                mixupIpInfos: mixupIpInfos
            }))
            rclient.expireat(categoryDomainBlockInfoKey, parseInt((+new Date) / 1000) + expiring);
        } catch (e) {
            log.info("get pure category ips failed", e)
        }
        return pureCategoryIps;
    }
    async applyNewDomain(ip, domain) {
        await this.updateIpBlockInfo(ip, domain, 'newDomain')
    }
    async scheduleRefreshBlockLevel() {
        const ipBlockKeys = await rclient.keysAsync("ip:block:info:*");
        log.info('schedule refresh block level for these ips:', ipBlockKeys)
        ipBlockKeys.map(async (key) => {
            let ipBlockInfo = JSON.parse(await rclient.getAsync(key));
            const { targetDomains, ip, blockLevel, blockSet } = ipBlockInfo;
            const allDomains = await dnsTool.getAllDns(ip);
            const sharedDomains = _.differenceWith(allDomains, targetDomains, (a, b) => {
                return this.domainCovered(b, a);
            });
            if (sharedDomains.length == 0 && blockLevel == 'domain') {
                Block.block(ip, blockSet)
            }
            if (sharedDomains.length > 0 && blockLevel == 'ip') {
                Block.unblock(ip, blockSet);
            }
            ipBlockInfo.ts = new Date() / 1000;
            ipBlockInfo.sharedDomains = sharedDomains;
            ipBlockInfo.allDomains = allDomains;
            await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
        })
    }
    async updateIpBlockInfo(ip, domain, action, blockSet = 'block_domain_set') {
        let ipBlockInfo = {
            blockSet: blockSet,
            ip: ip,
            targetDomains: [],
            sharedDomains: [],
            allDomains: [],
            ts: new Date() / 1000
        }
        if (!fc.isFeatureOn(featureName)) {
            ipBlockInfo.blockLevel = 'ip';
        } else {
            const key = this.ipBlockInfoKey(ip);
            const exist = (await rclient.existsAsync(key) == 1);
            if (exist) {
                ipBlockInfo = JSON.parse(await rclient.getAsync(key));
            }
            switch (action) {
                case 'block': {
                    // if the ip shared with other domain, should not apply ip level block
                    // if a.com and b.com share ip and one of them block, it should be domain level
                    // if both block, should update to ip level
                    !ipBlockInfo.targetDomains.includes(domain) && ipBlockInfo.targetDomains.push(domain);
                    const allDomains = await dnsTool.getAllDns(ip);
                    const sharedDomains = _.differenceWith(allDomains, ipBlockInfo.targetDomains, (a, b) => {
                        return this.domainCovered(b, a);
                    });
                    sharedDomains.length > 0 && log.info(`${ipBlockInfo.targetDomains.join(',')} ip ${ip} shared with domains ${sharedDomains.join(',')}`)
                    if (sharedDomains.length == 0) {
                        ipBlockInfo.blockLevel = 'ip';
                    } else {
                        ipBlockInfo.blockLevel = 'domain';
                    }
                    ipBlockInfo.sharedDomains = sharedDomains;
                    ipBlockInfo.allDomains = allDomains;
                    ipBlockInfo.ts = new Date() / 1000;
                    await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
                    break;
                }
                case 'unblock': {
                    !ipBlockInfo.sharedDomains.includes(domain) && ipBlockInfo.sharedDomains.push(domain);
                    ipBlockInfo.targetDomains = _.filter(ipBlockInfo.targetDomains, (a) => {
                        return a != domain;
                    })
                    if (ipBlockInfo.targetDomains.length == 0) {
                        await rclient.delAsync(key);
                    } else {
                        ipBlockInfo.ts = new Date() / 1000;
                        ipBlockInfo.blockLevel = 'domain';
                        await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
                    }
                    break;
                }
                case 'newDomain': {
                    if (exist) {
                        // it is old ip and new domain
                        const { blockSet, targetDomains } = ipBlockInfo;
                        const alreayExistInTargetDomains = _.find(targetDomains, (targetDomain) => {
                            return this.domainCovered(targetDomain, domain);
                        })
                        if (!alreayExistInTargetDomains) {
                            if (ipBlockInfo.blockLevel == 'ip') {
                                log.info('ip block level change when new doamin comming', ip, domain)
                                ipBlockInfo.blockLevel = 'domain';
                                Block.unblock(ip, blockSet);
                            }
                            ipBlockInfo.sharedDomains.push(domain);
                            ipBlockInfo.allDomains = await dnsTool.getAllDns(ip);
                            ipBlockInfo.ts = new Date() / 1000;
                            await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
                        }
                    }
                    break;
                }
            }
        }
        await this.updateDomainBlockInfo(domain, ipBlockInfo);
        return ipBlockInfo;
    }
    async updateDomainBlockInfo(domain, ipBlockInfo) {
        const key = this.domainBlockInfoKey(domain);
        let domainBlockInfo = await rclient.getAsync(key);
        try {
            domainBlockInfo = JSON.parse(domainBlockInfo) || {};
        } catch (err) {
            domainBlockInfo = {};
        }
        domainBlockInfo[ipBlockInfo.ip] = ipBlockInfo;
        await rclient.setAsync(key, JSON.stringify(domainBlockInfo));
        rclient.expireat(key, parseInt((+new Date) / 1000) + expiring);
    }
    domainCovered(blockDomain, otherDomain) {
        // a.b.com covred x.a.b.com
        if (!otherDomain) return true;
        if (blockDomain.startsWith('*.')) blockDomain = blockDomain.substring(2)
        if (otherDomain.startsWith('*.')) otherDomain = otherDomain.substring(2)
        const h1Sections = blockDomain.split('.').reverse();
        const h2Sections = otherDomain.split('.').reverse();
        for (let i = 0; i < h1Sections.length; i++) {
            if (h1Sections[i] !== h2Sections[i])
                return false;
        }
        return true;
    }
}

module.exports = BlockManager
<commit_msg>update<commit_after>/*    Copyright 2020 Firewalla Inc
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

const log = require("../net2/logger.js")(__filename);
const rclient = require('../util/redis_manager.js').getRedisClient();
const sem = require('../sensor/SensorEventManager.js').getInstance();
const Block = require('./Block.js');
const DNSTool = require('../net2/DNSTool.js')
const dnsTool = new DNSTool()
const IntelTool = require('../net2/IntelTool');
const intelTool = new IntelTool();
const _ = require('lodash');
const fc = require('../net2/config.js');
const featureName = 'smart_block';
let instance = null;
const expiring = 24 * 60 * 60 * 7;  // seven days

class BlockManager {
    constructor() {
        if (instance == null) {
            instance = this;
            const refreshInterval = 15 * 60; // 15 mins
            sem.once('IPTABLES_READY', async () => {
                if (fc.isFeatureOn(featureName)) {
                    this.scheduleId = setInterval(() => {
                        this.scheduleRefreshBlockLevel();
                    }, refreshInterval * 1000)
                }
                fc.onFeature(featureName, async (feature, status) => {
                    if (feature !== featureName) {
                        return
                    }
                    await this.reenforcePolicies();
                    if (status) {
                        this.scheduleId = setInterval(() => {
                            this.scheduleRefreshBlockLevel();
                        }, refreshInterval * 1000)
                    } else {
                        clearInterval(this.scheduleId);
                    }
                })
            })
        }
        return instance
    }
    async reenforcePolicies() {
        const PolicyManager2 = require('../alarm/PolicyManager2.js');
        const pm2 = new PolicyManager2();
        log.info('smart block enabled, re-enforce policies');
        const policies = await pm2.loadActivePoliciesAsync();
        policies.map((policy) => {
            pm2.tryPolicyEnforcement(policy, 'reenforce', policy);
        })
    }
    ipBlockInfoKey(ip) {
        return `ip:block:info:${ip}`
    }
    domainBlockInfoKey(domain) {
        return `domain:block:info:${domain}`
    }
    categoryDomainBlockInfoKey(domain) {
        return `category:block:info:${domain}`
    }
    getCategoryIpMapping(category) {
        return `rdns:category:${category}`
    }
    async getPureCategoryIps(category, categoryIps, originDomain) {
        if (!fc.isFeatureOn(featureName)) {
            return categoryIps;
        }
        const pureCategoryIps = [], mixupCategoryIps = [], mixupIpInfos = [];
        try {
            for (const categoryIp of categoryIps) {
                let pure = true;
                let mixupDomain;
                let mixupCategory;
                const domains = await dnsTool.getAllDns(categoryIp);
                for (const domain of domains) {
                    mixupDomain = domain;
                    const ips = await dnsTool.getIPsByDomain(domain);
                    for (const ip of ips) {
                        const intel = (await intelTool.getIntel(ip)) || {};
                        mixupCategory = intel.category;
                        pure = pure && intel.category == category;
                        if (!pure) break;
                    }
                    if (!pure) break;
                }
                if (pure) {
                    pureCategoryIps.push(categoryIp)
                } else {
                    mixupCategoryIps.push(categoryIp);
                    mixupIpInfos.push({
                        ip: categoryIp,
                        mixupDomain: mixupDomain,
                        mixupCategory: mixupCategory
                    })
                }
            }
            const categoryIpMappingKey = this.getCategoryIpMapping(category);
            mixupCategoryIps.length > 0 && await rclient.sremAsync(categoryIpMappingKey, mixupCategoryIps);
            pureCategoryIps.length > 0 && await rclient.saddAsync(categoryIpMappingKey, pureCategoryIps);
            const categoryDomainBlockInfoKey = this.categoryDomainBlockInfoKey(originDomain);
            await rclient.setAsync(categoryDomainBlockInfoKey, JSON.stringify({
                pureCategoryIps: pureCategoryIps,
                mixupIpInfos: mixupIpInfos
            }))
            rclient.expireat(categoryDomainBlockInfoKey, parseInt((+new Date) / 1000) + expiring);
        } catch (e) {
            log.info("get pure category ips failed", e)
        }
        return pureCategoryIps;
    }
    async applyNewDomain(ip, domain) {
        await this.updateIpBlockInfo(ip, domain, 'newDomain')
    }
    async scheduleRefreshBlockLevel() {
        const ipBlockKeys = await rclient.keysAsync("ip:block:info:*");
        log.info('schedule refresh block level for these ips:', ipBlockKeys)
        ipBlockKeys.map(async (key) => {
            let ipBlockInfo = JSON.parse(await rclient.getAsync(key));
            const { targetDomains, ip, blockLevel, blockSet } = ipBlockInfo;
            const allDomains = await dnsTool.getAllDns(ip);
            const sharedDomains = _.differenceWith(allDomains, targetDomains, (a, b) => {
                return this.domainCovered(b, a);
            });
            if (sharedDomains.length == 0 && blockLevel == 'domain') {
                Block.block(ip, blockSet)
            }
            if (sharedDomains.length > 0 && blockLevel == 'ip') {
                Block.unblock(ip, blockSet);
            }
            ipBlockInfo.ts = new Date() / 1000;
            ipBlockInfo.sharedDomains = sharedDomains;
            ipBlockInfo.allDomains = allDomains;
            await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
        })
    }
    async updateIpBlockInfo(ip, domain, action, blockSet = 'block_domain_set') {
        let ipBlockInfo = {
            blockSet: blockSet,
            ip: ip,
            targetDomains: [],
            sharedDomains: [],
            allDomains: [],
            ts: new Date() / 1000
        }
        if (!fc.isFeatureOn(featureName)) {
            ipBlockInfo.blockLevel = 'ip';
        } else {
            const key = this.ipBlockInfoKey(ip);
            const exist = (await rclient.existsAsync(key) == 1);
            if (exist) {
                ipBlockInfo = JSON.parse(await rclient.getAsync(key));
            }
            switch (action) {
                case 'block': {
                    // if the ip shared with other domain, should not apply ip level block
                    // if a.com and b.com share ip and one of them block, it should be domain level
                    // if both block, should update to ip level
                    !ipBlockInfo.targetDomains.includes(domain) && ipBlockInfo.targetDomains.push(domain);
                    const allDomains = await dnsTool.getAllDns(ip);
                    const sharedDomains = _.differenceWith(allDomains, ipBlockInfo.targetDomains, (a, b) => {
                        return this.domainCovered(b, a);
                    });
                    sharedDomains.length > 0 && log.info(`${ipBlockInfo.targetDomains.join(',')} ip ${ip} shared with domains ${sharedDomains.join(',')}`)
                    if (sharedDomains.length == 0) {
                        ipBlockInfo.blockLevel = 'ip';
                    } else {
                        ipBlockInfo.blockLevel = 'domain';
                    }
                    ipBlockInfo.sharedDomains = sharedDomains;
                    ipBlockInfo.allDomains = allDomains;
                    ipBlockInfo.ts = new Date() / 1000;
                    await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
                    break;
                }
                case 'unblock': {
                    !ipBlockInfo.sharedDomains.includes(domain) && ipBlockInfo.sharedDomains.push(domain);
                    ipBlockInfo.targetDomains = _.filter(ipBlockInfo.targetDomains, (a) => {
                        return a != domain;
                    })
                    if (ipBlockInfo.targetDomains.length == 0) {
                        await rclient.delAsync(key);
                    } else {
                        ipBlockInfo.ts = new Date() / 1000;
                        ipBlockInfo.blockLevel = 'domain';
                        await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
                    }
                    break;
                }
                case 'newDomain': {
                    if (exist) {
                        // it is old ip and new domain
                        const { blockSet, targetDomains } = ipBlockInfo;
                        const alreayExistInTargetDomains = _.find(targetDomains, (targetDomain) => {
                            return this.domainCovered(targetDomain, domain);
                        })
                        if (!alreayExistInTargetDomains) {
                            if (ipBlockInfo.blockLevel == 'ip') {
                                log.info('ip block level change when new doamin comming', ip, domain)
                                ipBlockInfo.blockLevel = 'domain';
                                Block.unblock(ip, blockSet);
                            }
                            ipBlockInfo.sharedDomains.push(domain);
                            ipBlockInfo.allDomains = await dnsTool.getAllDns(ip);
                            ipBlockInfo.ts = new Date() / 1000;
                            await rclient.setAsync(key, JSON.stringify(ipBlockInfo));
                        }
                    }
                    break;
                }
            }
        }
        await this.updateDomainBlockInfo(domain, ipBlockInfo);
        return ipBlockInfo;
    }
    async updateDomainBlockInfo(domain, ipBlockInfo) {
        const key = this.domainBlockInfoKey(domain);
        let domainBlockInfo = await rclient.getAsync(key);
        try {
            domainBlockInfo = JSON.parse(domainBlockInfo) || {};
        } catch (err) {
            domainBlockInfo = {};
        }
        domainBlockInfo[ipBlockInfo.ip] = ipBlockInfo;
        await rclient.setAsync(key, JSON.stringify(domainBlockInfo));
        rclient.expireat(key, parseInt((+new Date) / 1000) + expiring);
    }
    domainCovered(blockDomain, otherDomain) {
        // a.b.com covred x.a.b.com
        if (!otherDomain) return true;
        if (blockDomain.startsWith('*.')) blockDomain = blockDomain.substring(2)
        if (otherDomain.startsWith('*.')) otherDomain = otherDomain.substring(2)
        const h1Sections = blockDomain.split('.').reverse();
        const h2Sections = otherDomain.split('.').reverse();
        for (let i = 0; i < h1Sections.length; i++) {
            if (h1Sections[i] !== h2Sections[i])
                return false;
        }
        return true;
    }
}

module.exports = BlockManager
<|endoftext|>


<commit_before>/**
 * indexed db adapter
 * ===
 * - originally authored by Vivian Li
 *
 */

Lawnchair.adapter('indexed-db', (function(){

  function fail(e, i) { console.error('error in indexed-db adapter!', e, i); }

  var STORE_NAME = 'lawnchair';

  var getIDB = function() {
    return window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.oIndexedDB || window.msIndexedDB;
  };
  var getIDBTransaction = function() {
      return window.IDBTransaction || window.webkitIDBTransaction ||
          window.mozIDBTransaction || window.oIDBTransaction ||
          window.msIDBTransaction;
  };
  var getIDBKeyRange = function() {
      return window.IDBKeyRange || window.webkitIDBKeyRange ||
          window.mozIDBKeyRange || window.oIDBKeyRange ||
          window.msIDBKeyRange;
  };

  return {

    valid: function() { return !!getIDB(); },

    init:function(options, callback) {
        this.idb = getIDB();
        this.waiting = [];
        var request = this.idb.open(this.name);
        var self = this;
        var cb = self.fn(self.name, callback);
        var win = function(){ return cb.call(self, self); }

        request.onsuccess = function(event) {
           self.db = request.result;

            if(self.db.version != "1.0") {
              var setVrequest = self.db.setVersion("1.0");
              // onsuccess is the only place we can create Object Stores
              setVrequest.onsuccess = function(e) {
                  self.store = self.db.createObjectStore(STORE_NAME, { autoIncrement: true} );
                  for (var i = 0; i < self.waiting.length; i++) {
                      self.waiting[i].call(self);
                  }
                  self.waiting = [];
                  win();
              };
              setVrequest.onerror = function(e) {
                  console.log("Failed to create objectstore " + e);
                  fail(e);
              }
            } else {
                self.store = {};
                for (var i = 0; i < self.waiting.length; i++) {
                      self.waiting[i].call(self);
                }
                self.waiting = [];
                win();
            }
        }
        request.onerror = fail;
    },

    save:function(obj, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.save(obj, callback);
            });
            return;
         }

         var self = this;
         var win  = function (e) { if (callback) { obj.key = e.target.result; self.lambda(callback).call(self, obj) }};

         var trans = this.db.transaction(STORE_NAME, getIDBTransaction().READ_WRITE);
         var store = trans.objectStore(STORE_NAME);
         var request = obj.key ? store.put(obj, obj.key) : store.put(obj);

         request.onsuccess = win;
         request.onerror = fail;

         return this;
    },

    // FIXME this should be a batch insert / just getting the test to pass...
    batch: function (objs, cb) {

        var results = []
        ,   done = false
        ,   self = this

        var updateProgress = function(obj) {
            results.push(obj)
            done = results.length === objs.length
        }

        var checkProgress = setInterval(function() {
            if (done) {
                if (cb) self.lambda(cb).call(self, results)
                clearInterval(checkProgress)
            }
        }, 200)

        for (var i = 0, l = objs.length; i < l; i++)
            this.save(objs[i], updateProgress)

        return this
    },

    get:function(key, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.get(key, callback);
            });
            return;
        }

        var self = this;
        var win  = function (e) { if (callback) { self.lambda(callback).call(self, e.target.result) }};

        if (!this.isArray(key)){
            var req = this.db.transaction(STORE_NAME).objectStore(STORE_NAME).get(key);

            req.onsuccess = win;
            req.onerror = function(event) {
                console.log("Failed to find " + key);
                fail(event);
            };

        // FIXME: again the setInterval solution to async callbacks..
        } else {

            // note: these are hosted.
            var results = []
            ,   done = false
            ,   keys = key

            var updateProgress = function(obj) {
                results.push(obj)
                done = results.length === keys.length
            }

            var checkProgress = setInterval(function() {
                if (done) {
                    if (callback) self.lambda(callback).call(self, results)
                    clearInterval(checkProgress)
                }
            }, 200)

            for (var i = 0, l = keys.length; i < l; i++)
                this.get(keys[i], updateProgress)

        }

        return this;
    },

    exists:function(key, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.exists(key, callback);
            });
            return;
        }

        var self = this;

        var req = this.db.transaction(STORE_NAME).objectStore(STORE_NAME).openCursor(getIDBKeyRange().only(key));

        req.onsuccess = function(event) {
            // exists iff req.result is not null
            self.lambda(callback).call(self, event.target.result !== null)
        };
        req.onerror = function(event) {
            console.log("Failed to test for " + key);
            fail(event);
        };

        return this;
    },

    all:function(callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.all(callback);
            });
            return;
        }
        var cb = this.fn(this.name, callback) || undefined;
        var self = this;
        var objectStore = this.db.transaction(STORE_NAME).objectStore(STORE_NAME);
        var toReturn = [];
        objectStore.openCursor().onsuccess = function(event) {
          var cursor = event.target.result;
          if (cursor) {
               toReturn.push(cursor.value);
               cursor.continue();
          }
          else {
              if (cb) cb.call(self, toReturn);
          }
        };
        return this;
    },

    remove:function(keyOrObj, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.remove(keyOrObj, callback);
            });
            return;
        }
        if (typeof keyOrObj == "object") {
            keyOrObj = keyOrObj.key;
        }
        var self = this;
        var win  = function () { if (callback) self.lambda(callback).call(self) };

        var request = this.db.transaction(STORE_NAME, getIDBTransaction().READ_WRITE).objectStore(STORE_NAME).delete(keyOrObj);
        request.onsuccess = win;
        request.onerror = fail;
        return this;
    },

    nuke:function(callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.nuke(callback);
            });
            return;
        }

        var self = this
        ,   win  = callback ? function() { self.lambda(callback).call(self) } : function(){};

        try {
            this.db
                .transaction(STORE_NAME, getIDBTransaction().READ_WRITE)
                .objectStore(STORE_NAME).clear().onsuccess = win;

        } catch(e) {
            fail();
        }
        return this;
    }

  };

})());
<commit_msg>'continue' and 'delete' are somehow reserved<commit_after>/**
 * indexed db adapter
 * ===
 * - originally authored by Vivian Li
 *
 */

Lawnchair.adapter('indexed-db', (function(){

  function fail(e, i) { console.error('error in indexed-db adapter!', e, i); }

  var STORE_NAME = 'lawnchair';

  var getIDB = function() {
    return window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.oIndexedDB || window.msIndexedDB;
  };
  var getIDBTransaction = function() {
      return window.IDBTransaction || window.webkitIDBTransaction ||
          window.mozIDBTransaction || window.oIDBTransaction ||
          window.msIDBTransaction;
  };
  var getIDBKeyRange = function() {
      return window.IDBKeyRange || window.webkitIDBKeyRange ||
          window.mozIDBKeyRange || window.oIDBKeyRange ||
          window.msIDBKeyRange;
  };

  return {

    valid: function() { return !!getIDB(); },

    init:function(options, callback) {
        this.idb = getIDB();
        this.waiting = [];
        var request = this.idb.open(this.name);
        var self = this;
        var cb = self.fn(self.name, callback);
        var win = function(){ return cb.call(self, self); }

        request.onsuccess = function(event) {
           self.db = request.result;

            if(self.db.version != "1.0") {
              var setVrequest = self.db.setVersion("1.0");
              // onsuccess is the only place we can create Object Stores
              setVrequest.onsuccess = function(e) {
                  self.store = self.db.createObjectStore(STORE_NAME, { autoIncrement: true} );
                  for (var i = 0; i < self.waiting.length; i++) {
                      self.waiting[i].call(self);
                  }
                  self.waiting = [];
                  win();
              };
              setVrequest.onerror = function(e) {
                  console.log("Failed to create objectstore " + e);
                  fail(e);
              }
            } else {
                self.store = {};
                for (var i = 0; i < self.waiting.length; i++) {
                      self.waiting[i].call(self);
                }
                self.waiting = [];
                win();
            }
        }
        request.onerror = fail;
    },

    save:function(obj, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.save(obj, callback);
            });
            return;
         }

         var self = this;
         var win  = function (e) { if (callback) { obj.key = e.target.result; self.lambda(callback).call(self, obj) }};

         var trans = this.db.transaction(STORE_NAME, getIDBTransaction().READ_WRITE);
         var store = trans.objectStore(STORE_NAME);
         var request = obj.key ? store.put(obj, obj.key) : store.put(obj);

         request.onsuccess = win;
         request.onerror = fail;

         return this;
    },

    // FIXME this should be a batch insert / just getting the test to pass...
    batch: function (objs, cb) {

        var results = []
        ,   done = false
        ,   self = this

        var updateProgress = function(obj) {
            results.push(obj)
            done = results.length === objs.length
        }

        var checkProgress = setInterval(function() {
            if (done) {
                if (cb) self.lambda(cb).call(self, results)
                clearInterval(checkProgress)
            }
        }, 200)

        for (var i = 0, l = objs.length; i < l; i++)
            this.save(objs[i], updateProgress)

        return this
    },

    get:function(key, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.get(key, callback);
            });
            return;
        }

        var self = this;
        var win  = function (e) { if (callback) { self.lambda(callback).call(self, e.target.result) }};

        if (!this.isArray(key)){
            var req = this.db.transaction(STORE_NAME).objectStore(STORE_NAME).get(key);

            req.onsuccess = win;
            req.onerror = function(event) {
                console.log("Failed to find " + key);
                fail(event);
            };

        // FIXME: again the setInterval solution to async callbacks..
        } else {

            // note: these are hosted.
            var results = []
            ,   done = false
            ,   keys = key

            var updateProgress = function(obj) {
                results.push(obj)
                done = results.length === keys.length
            }

            var checkProgress = setInterval(function() {
                if (done) {
                    if (callback) self.lambda(callback).call(self, results)
                    clearInterval(checkProgress)
                }
            }, 200)

            for (var i = 0, l = keys.length; i < l; i++)
                this.get(keys[i], updateProgress)

        }

        return this;
    },

    exists:function(key, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.exists(key, callback);
            });
            return;
        }

        var self = this;

        var req = this.db.transaction(STORE_NAME).objectStore(STORE_NAME).openCursor(getIDBKeyRange().only(key));

        req.onsuccess = function(event) {
            // exists iff req.result is not null
            self.lambda(callback).call(self, event.target.result !== null)
        };
        req.onerror = function(event) {
            console.log("Failed to test for " + key);
            fail(event);
        };

        return this;
    },

    all:function(callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.all(callback);
            });
            return;
        }
        var cb = this.fn(this.name, callback) || undefined;
        var self = this;
        var objectStore = this.db.transaction(STORE_NAME).objectStore(STORE_NAME);
        var toReturn = [];
        objectStore.openCursor().onsuccess = function(event) {
          var cursor = event.target.result;
          if (cursor) {
               toReturn.push(cursor.value);
               cursor['continue']();
          }
          else {
              if (cb) cb.call(self, toReturn);
          }
        };
        return this;
    },

    remove:function(keyOrObj, callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.remove(keyOrObj, callback);
            });
            return;
        }
        if (typeof keyOrObj == "object") {
            keyOrObj = keyOrObj.key;
        }
        var self = this;
        var win  = function () { if (callback) self.lambda(callback).call(self) };

        var request = this.db.transaction(STORE_NAME, getIDBTransaction().READ_WRITE).objectStore(STORE_NAME)['delete'](keyOrObj);
        request.onsuccess = win;
        request.onerror = fail;
        return this;
    },

    nuke:function(callback) {
        if(!this.store) {
            this.waiting.push(function() {
                this.nuke(callback);
            });
            return;
        }

        var self = this
        ,   win  = callback ? function() { self.lambda(callback).call(self) } : function(){};

        try {
            this.db
                .transaction(STORE_NAME, getIDBTransaction().READ_WRITE)
                .objectStore(STORE_NAME).clear().onsuccess = win;

        } catch(e) {
            fail();
        }
        return this;
    }

  };

})());
<|endoftext|>


<commit_before>/*jslint unparam: true, node: true, plusplus: true, nomen: true, indent: 2, todo: true */
'use strict';

var _             = require('underscore');
var moment        = require('moment');
var Mongoose      = require('mongoose');
var Mailer        = require('../lib/mailer');
var BookingModel  = require('../models/booking');
var GadgetModel   = require('../models/gadget');

/**
 * @private
 */
function renderBookings(res, gadget, booking, error) {
  res.render('bookings/edit', {
    gadget: gadget,
    booking: booking,
    gadgetId: gadget._id,
    startdate: booking.startdate,
    enddate: booking.enddate,
    starttime: booking.starttime,
    endtime: booking.endtime,
    error: error
  });
}

var BookingsController = {

  /**
   * Lists all open and closed bookings for the current user. When the current
   * user has admin permissions, all bookings are shown.
   *
   * @todo: Pagination?
   */
  listAll: function (req, res, next) {
    var where = {};

    if (req.session.user.role !== 'admin') {
      // limit bookings to current user when users role is not admin
      where.user = req.session.user._id;
    }

    BookingModel.find(where)
      .sort({
        'user': 1,
        'startdate': 1
      })
      .populate('user')
      .populate('gadget')
      .exec(function (err, bookings) {
        if (err) { return next(err); }

        if (req.session.user.role === 'admin') {
          // render admin view
          res.render('bookings/list-admin', {
            title: 'bookings',
            bookings : _.groupBy(bookings, 'status')
          });
        } else {
          // render user view
          res.render('bookings/list', {
            title: 'bookings',
            bookings: bookings
          });
        }
      });
  },

  create: function (req, res, next) {
    GadgetModel.findById(req.params.id, function (err, gadget) {
      res.render('bookings/edit', {
        booking: new BookingModel(),
        gadget: gadget,
        gadgetId: gadget._id,
        startdate: moment().add(1, 'day').format('YYYY-MM-DD'),
        enddate: moment().add(2, 'days').format('YYYY-MM-DD'),
        starttime: moment().add(1, 'hour').format('HH:00'),
        endtime: moment().add(1, 'hour').format('HH:00')
      });
    });
  },

  edit: function (req, res, next) {
    BookingModel.findById(req.params.id, function (err, booking) {
      if (err) { return next(err); }

      res.render('bookings/edit', {
        booking: booking,
        gadgetId: booking.gadget,
        startdate: moment(booking.start).format('YYYY-MM-DD'),
        starttime: moment(booking.start).format('HH:mm'),
        enddate: moment(booking.end).format('YYYY-MM-DD'),
        endtime: moment(booking.end).format('HH:mm'),
      });
    });
  },

  save: function (req, res, next) {

    var sBooking, eBooking, error, bookingId;

    bookingId = req.body._id || new Mongoose.Types.ObjectId();

    sBooking = new Date(req.body.startdate + ' ' + req.body.starttime);
    eBooking = new Date(req.body.enddate + ' ' + req.body.endtime);

    if (sBooking.getTime() < Date.now()) {
      error = 'Start date not valid';
    } else if (moment.utc(eBooking) < moment.utc(sBooking)) {
      error = 'End date not valid';
    }

    GadgetModel.findById(req.params.id, function (err, gadget) {

      if (error) {
        // start or end date not valid -> return
        return renderBookings(res, gadget, req.body, error);
      }

      BookingModel.count({
        // count bookings colliding with requested date range and exclude the
        // current booking (on updates)...
        _id: { $ne: bookingId },
        gadget: gadget._id,
        start: { $lte: eBooking },
        end: { $gte: sBooking }
      }, function (err, bookings) {
        if (err) { return next(err); }

        if (bookings !== 0) {
          // ...if number of bookings is not zero, the gadget is not available
          error = 'Gadget not available in selected time range';
          return renderBookings(res, gadget, req.body, error);
        }

        // gadget available -> create booking entry
        BookingModel.findByIdAndUpdate(
          bookingId,
          {
            gadget: gadget._id,
            gadgetname: gadget.name,
            user: req.session.user._id,
            username: req.session.user.displayname,
            start: sBooking,
            end: eBooking,
            notificationSent: false
          },
          {
            upsert: true
          },
          function (err, booking) {
            if (err) { return next(err); }

            if (req.body._id) {
              Mailer.sendBookingUpdatedMail(gadget, booking, req.session.user);
            } else {
              Mailer.sendNewBookingMail(gadget, booking, req.session.user);
            }

            res.render('bookings/ok', { gadget : gadget });
          }
        );
      });
    });
  },

  handout: function (req, res, next) {
    BookingModel.findByIdAndUpdate(
      req.params.id,
      {
        $set: {
          status: 'handout',
          handoutdate: new Date(),
          handoutuser: req.session.user._id
        }
      },
      function (err, booking) {
        if (err) { return next(err); }

        GadgetModel.findByIdAndUpdate(
          booking.gadget,
          {
            $inc: { handoutcount: 1 }
          },
          function (err, result) {
            res.redirect('/bookings/');
          }
        );
      }
    );
  },

  takeback: function (req, res, next) {
    BookingModel.findByIdAndUpdate(
      req.params.id,
      {
        $set: {
          status: 'closed',
          closedate: new Date(),
          closeduser: req.session.user._id
        }
      },
      function (err, booking) {
        if (err) { return next(err); }

        res.redirect('/bookings/');
      }
    );
  },

  remove: function (req, res, next) {
    BookingModel.remove({ _id: req.params.id }, function (err, result) {
      if (err) { return next(err); }

      // redirect to last page
      res.redirect(req.headers.referer);
    });
  }

};

module.exports = BookingsController;<commit_msg>Fixed booking validation<commit_after>/*jslint unparam: true, node: true, plusplus: true, nomen: true, indent: 2, todo: true */
'use strict';

var _             = require('underscore');
var moment        = require('moment');
var Mongoose      = require('mongoose');
var Mailer        = require('../lib/mailer');
var BookingModel  = require('../models/booking');
var GadgetModel   = require('../models/gadget');

/**
 * @private
 */
function renderBookings(res, gadget, booking, error) {
  res.render('bookings/edit', {
    gadget: gadget,
    booking: booking,
    gadgetId: gadget._id,
    startdate: booking.startdate,
    enddate: booking.enddate,
    starttime: booking.starttime,
    endtime: booking.endtime,
    error: error
  });
}

var BookingsController = {

  /**
   * Lists all open and closed bookings for the current user. When the current
   * user has admin permissions, all bookings are shown.
   *
   * @todo: Pagination?
   */
  listAll: function (req, res, next) {
    var where = {};

    if (req.session.user.role !== 'admin') {
      // limit bookings to current user when users role is not admin
      where.user = req.session.user._id;
    }

    BookingModel.find(where)
      .sort({
        'user': 1,
        'startdate': 1
      })
      .populate('user')
      .populate('gadget')
      .exec(function (err, bookings) {
        if (err) { return next(err); }

        if (req.session.user.role === 'admin') {
          // render admin view
          res.render('bookings/list-admin', {
            title: 'bookings',
            bookings : _.groupBy(bookings, 'status')
          });
        } else {
          // render user view
          res.render('bookings/list', {
            title: 'bookings',
            bookings: bookings
          });
        }
      });
  },

  create: function (req, res, next) {
    GadgetModel.findById(req.params.id, function (err, gadget) {
      res.render('bookings/edit', {
        booking: new BookingModel(),
        gadget: gadget,
        gadgetId: gadget._id,
        startdate: moment().add(1, 'day').format('YYYY-MM-DD'),
        enddate: moment().add(2, 'days').format('YYYY-MM-DD'),
        starttime: moment().add(1, 'hour').format('HH:00'),
        endtime: moment().add(1, 'hour').format('HH:00')
      });
    });
  },

  edit: function (req, res, next) {
    BookingModel.findById(req.params.id, function (err, booking) {
      if (err) { return next(err); }

      res.render('bookings/edit', {
        booking: booking,
        gadgetId: booking.gadget,
        startdate: moment(booking.start).format('YYYY-MM-DD'),
        starttime: moment(booking.start).format('HH:mm'),
        enddate: moment(booking.end).format('YYYY-MM-DD'),
        endtime: moment(booking.end).format('HH:mm'),
      });
    });
  },

  save: function (req, res, next) {

    var sBooking, eBooking, error;

    sBooking = new Date(req.body.startdate + ' ' + req.body.starttime);
    eBooking = new Date(req.body.enddate + ' ' + req.body.endtime);

    if (sBooking.getTime() < Date.now()) {
      error = 'Start date not valid';
    } else if (eBooking.getTime() < sBooking.getTime()) {
      error = 'End date not valid';
    }

    GadgetModel.findById(req.params.id, function (err, gadget) {

      if (error) {
        // start or end date not valid -> return
        return renderBookings(res, gadget, req.body, error);
      }

      BookingModel.count({
        // count bookings colliding with requested date range and exclude the
        // current booking (on updates)...
        _id: { $ne: req.body._id },
        gadget: gadget._id,
        start: { $lte: eBooking },
        end: { $gte: sBooking }
      }, function (err, bookings) {
        if (err) { return next(err); }

        if (bookings !== 0) {
          // ...if number of bookings is not zero, the gadget is not available
          error = 'Gadget not available in selected time range';
          return renderBookings(res, gadget, req.body, error);
        }

        // gadget available -> create booking entry
        BookingModel.findByIdAndUpdate(
          req.body._id,
          {
            gadget: gadget._id,
            gadgetname: gadget.name,
            user: req.session.user._id,
            username: req.session.user.displayname,
            start: sBooking,
            end: eBooking,
            notificationSent: false
          },
          {
            upsert: true
          },
          function (err, booking) {
            if (err) { return next(err); }

            if (req.body._id) {
              Mailer.sendBookingUpdatedMail(gadget, booking, req.session.user);
            } else {
              Mailer.sendNewBookingMail(gadget, booking, req.session.user);
            }

            res.render('bookings/ok', { gadget : gadget });
          }
        );
      });
    });
  },

  handout: function (req, res, next) {
    BookingModel.findByIdAndUpdate(
      req.params.id,
      {
        $set: {
          status: 'handout',
          handoutdate: new Date(),
          handoutuser: req.session.user._id
        }
      },
      function (err, booking) {
        if (err) { return next(err); }

        GadgetModel.findByIdAndUpdate(
          booking.gadget,
          {
            $inc: { handoutcount: 1 }
          },
          function (err, result) {
            res.redirect('/bookings/');
          }
        );
      }
    );
  },

  takeback: function (req, res, next) {
    BookingModel.findByIdAndUpdate(
      req.params.id,
      {
        $set: {
          status: 'closed',
          closedate: new Date(),
          closeduser: req.session.user._id
        }
      },
      function (err, booking) {
        if (err) { return next(err); }

        res.redirect('/bookings/');
      }
    );
  },

  remove: function (req, res, next) {
    BookingModel.remove({ _id: req.params.id }, function (err, result) {
      if (err) { return next(err); }

      // redirect to last page
      res.redirect(req.headers.referer);
    });
  }

};

module.exports = BookingsController;<|endoftext|>