<commit_before>'use strict'

const Scheduler = require('qos_scheduler')
const Performance = require('qos_performance')
const Process = require('qos_process')

global.BUCKET_EMERGENCY = 1000
global.BUCKET_FLOOR = 2000
global.BUCKET_CEILING = 9500
const CPU_BUFFER = 130
const CPU_MINIMUM = 0.50
const CPU_ADJUST = 0.05
const CPU_GLOBAL_BOOST = 60
const GLOBAL_LAST_RESET = Game.time

class QosKernel {
  constructor () {
    global.kernel = this

    if (!Memory.qos) {
      Memory.qos = {}
    }
    this.newglobal = GLOBAL_LAST_RESET === Game.time
    this.simulation = !!Game.rooms['sim']
    this.scheduler = new Scheduler()
    this.performance = new Performance()
    this.process = Process
  }

  start () {
    // Announce new uploads
    Logger.log(`Initializing Kernel for tick ${Game.time}`, LOG_INFO, 'kernel')
    if (!Memory.qos.script_version || Memory.qos.script_version !== SCRIPT_VERSION) {
      Logger.log(`New script upload detected: ${SCRIPT_VERSION}`, LOG_WARN)
      Memory.qos.script_version = SCRIPT_VERSION
      Memory.qos.script_upload = Game.time
    }

    sos.lib.segments.moveToGlobalCache()
    sos.lib.stormtracker.track()

    if (Game.time % 7 === 0) {
      this.cleanMemory()
    }

    this.scheduler.shift()

    if (this.scheduler.getProcessCount() <= 0) {
      this.scheduler.launchProcess('player')
    }
  }

  cleanMemory () {
    let i
    for (i in Memory.creeps) { // jshint ignore:line
      if (!Game.creeps[i]) {
        delete Memory.creeps[i]
      }
    }

    sos.lib.cache.clean()
    qlib.notify.clean()
  }

  run () {
    while (this.shouldContinue()) {
      const runningProcess = this.scheduler.getNextProcess()
      if (!runningProcess) {
        return
      }
      Logger.defaultLogGroup = runningProcess.name
      try {
        let processName = runningProcess.name
        const descriptor = runningProcess.getDescriptor()
        if (descriptor) {
          processName += ' ' + descriptor
        }

        Logger.log(`Running ${processName} (pid ${runningProcess.pid})`, LOG_INFO, 'kernel')
        const startCpu = Game.cpu.getUsed()
        runningProcess.run()
        let performanceName = runningProcess.name
        const performanceDescriptor = runningProcess.getPerformanceDescriptor()
        if (performanceDescriptor) {
          performanceName += ' ' + performanceDescriptor
        }
        this.performance.addProgramStats(performanceName, Game.cpu.getUsed() - startCpu)
      } catch (err) {
        let message = 'program error occurred\n'
        message += `process ${runningProcess.pid}: ${runningProcess.name}\n`
        message += !!err && !!err.stack ? err.stack : err.toString()
        Logger.log(message, LOG_ERROR)
      }
      Logger.defaultLogGroup = 'default'
    }
  }

  sigmoid (x) {
    return 1.0 / (1.0 + Math.exp(-x))
  }

  sigmoidSkewed (x) {
    return this.sigmoid((x * 12.0) - 6.0)
  }

  shouldContinue () {
    return !!this.simulation || Game.cpu.getUsed() < this.getCpuLimit()
  }

  getCpuLimit () {
    if (Game.cpu.bucket > BUCKET_CEILING) {
      return Game.cpu.tickLimit - CPU_BUFFER
    }

    if (Game.cpu.bucket < BUCKET_EMERGENCY) {
      return 0
    }

    if (Game.cpu.bucket < BUCKET_FLOOR) {
      return Game.cpu.limit * CPU_MINIMUM
    }

    if (!this._cpuLimit) {
      const bucketRange = BUCKET_CEILING - BUCKET_FLOOR
      const depthInRange = (Game.cpu.bucket - BUCKET_FLOOR) / bucketRange
      const minToAllocate = Game.cpu.limit * CPU_MINIMUM
      const maxToAllocate = Game.cpu.limit
      this._cpuLimit = (minToAllocate + this.sigmoidSkewed(depthInRange) * (maxToAllocate - minToAllocate)) * (1 - CPU_ADJUST)
      if (this.newglobal) {
        this._cpuLimit += CPU_GLOBAL_BOOST
      }
    }

    return this._cpuLimit
  }

  shutdown () {
    sos.lib.vram.saveDirty()
    sos.lib.segments.process()

    const processCount = this.scheduler.getProcessCount()
    const completedCount = this.scheduler.memory.processes.completed.length

    Logger.log(`Processes Run: ${completedCount}/${processCount}`, LOG_INFO, 'kernel')
    Logger.log(`Tick Limit: ${Game.cpu.tickLimit}`, LOG_INFO, 'kernel')
    Logger.log(`Kernel Limit: ${this.getCpuLimit()}`, LOG_INFO, 'kernel')
    Logger.log(`CPU Used: ${Game.cpu.getUsed()}`, LOG_INFO, 'kernel')
    Logger.log(`Bucket: ${Game.cpu.bucket}`, LOG_INFO, 'kernel')

    if (Game.time % 50 === 0) {
      this.performance.reportHtml()
    }
    if (Game.time % 3000 === 0) {
      this.performance.clear()
    }
  }
}

module.exports = QosKernel
<commit_msg>Circleci complains about globals not being defined<commit_after>'use strict'

const Scheduler = require('qos_scheduler')
const Performance = require('qos_performance')
const Process = require('qos_process')

const BUCKET_EMERGENCY = 1000
const BUCKET_FLOOR = 2000
const BUCKET_CEILING = 9500
const CPU_BUFFER = 130
const CPU_MINIMUM = 0.50
const CPU_ADJUST = 0.05
const CPU_GLOBAL_BOOST = 60
const GLOBAL_LAST_RESET = Game.time
global.BUCKET_EMERGENCY = BUCKET_EMERGENCY
global.BUCKET_FLOOR = BUCKET_FLOOR
global.BUCKET_CEILING = BUCKET_CEILING

class QosKernel {
  constructor () {
    global.kernel = this

    if (!Memory.qos) {
      Memory.qos = {}
    }
    this.newglobal = GLOBAL_LAST_RESET === Game.time
    this.simulation = !!Game.rooms['sim']
    this.scheduler = new Scheduler()
    this.performance = new Performance()
    this.process = Process
  }

  start () {
    // Announce new uploads
    Logger.log(`Initializing Kernel for tick ${Game.time}`, LOG_INFO, 'kernel')
    if (!Memory.qos.script_version || Memory.qos.script_version !== SCRIPT_VERSION) {
      Logger.log(`New script upload detected: ${SCRIPT_VERSION}`, LOG_WARN)
      Memory.qos.script_version = SCRIPT_VERSION
      Memory.qos.script_upload = Game.time
    }

    sos.lib.segments.moveToGlobalCache()
    sos.lib.stormtracker.track()

    if (Game.time % 7 === 0) {
      this.cleanMemory()
    }

    this.scheduler.shift()

    if (this.scheduler.getProcessCount() <= 0) {
      this.scheduler.launchProcess('player')
    }
  }

  cleanMemory () {
    let i
    for (i in Memory.creeps) { // jshint ignore:line
      if (!Game.creeps[i]) {
        delete Memory.creeps[i]
      }
    }

    sos.lib.cache.clean()
    qlib.notify.clean()
  }

  run () {
    while (this.shouldContinue()) {
      const runningProcess = this.scheduler.getNextProcess()
      if (!runningProcess) {
        return
      }
      Logger.defaultLogGroup = runningProcess.name
      try {
        let processName = runningProcess.name
        const descriptor = runningProcess.getDescriptor()
        if (descriptor) {
          processName += ' ' + descriptor
        }

        Logger.log(`Running ${processName} (pid ${runningProcess.pid})`, LOG_INFO, 'kernel')
        const startCpu = Game.cpu.getUsed()
        runningProcess.run()
        let performanceName = runningProcess.name
        const performanceDescriptor = runningProcess.getPerformanceDescriptor()
        if (performanceDescriptor) {
          performanceName += ' ' + performanceDescriptor
        }
        this.performance.addProgramStats(performanceName, Game.cpu.getUsed() - startCpu)
      } catch (err) {
        let message = 'program error occurred\n'
        message += `process ${runningProcess.pid}: ${runningProcess.name}\n`
        message += !!err && !!err.stack ? err.stack : err.toString()
        Logger.log(message, LOG_ERROR)
      }
      Logger.defaultLogGroup = 'default'
    }
  }

  sigmoid (x) {
    return 1.0 / (1.0 + Math.exp(-x))
  }

  sigmoidSkewed (x) {
    return this.sigmoid((x * 12.0) - 6.0)
  }

  shouldContinue () {
    return !!this.simulation || Game.cpu.getUsed() < this.getCpuLimit()
  }

  getCpuLimit () {
    if (Game.cpu.bucket > BUCKET_CEILING) {
      return Game.cpu.tickLimit - CPU_BUFFER
    }

    if (Game.cpu.bucket < BUCKET_EMERGENCY) {
      return 0
    }

    if (Game.cpu.bucket < BUCKET_FLOOR) {
      return Game.cpu.limit * CPU_MINIMUM
    }

    if (!this._cpuLimit) {
      const bucketRange = BUCKET_CEILING - BUCKET_FLOOR
      const depthInRange = (Game.cpu.bucket - BUCKET_FLOOR) / bucketRange
      const minToAllocate = Game.cpu.limit * CPU_MINIMUM
      const maxToAllocate = Game.cpu.limit
      this._cpuLimit = (minToAllocate + this.sigmoidSkewed(depthInRange) * (maxToAllocate - minToAllocate)) * (1 - CPU_ADJUST)
      if (this.newglobal) {
        this._cpuLimit += CPU_GLOBAL_BOOST
      }
    }

    return this._cpuLimit
  }

  shutdown () {
    sos.lib.vram.saveDirty()
    sos.lib.segments.process()

    const processCount = this.scheduler.getProcessCount()
    const completedCount = this.scheduler.memory.processes.completed.length

    Logger.log(`Processes Run: ${completedCount}/${processCount}`, LOG_INFO, 'kernel')
    Logger.log(`Tick Limit: ${Game.cpu.tickLimit}`, LOG_INFO, 'kernel')
    Logger.log(`Kernel Limit: ${this.getCpuLimit()}`, LOG_INFO, 'kernel')
    Logger.log(`CPU Used: ${Game.cpu.getUsed()}`, LOG_INFO, 'kernel')
    Logger.log(`Bucket: ${Game.cpu.bucket}`, LOG_INFO, 'kernel')

    if (Game.time % 50 === 0) {
      this.performance.reportHtml()
    }
    if (Game.time % 3000 === 0) {
      this.performance.clear()
    }
  }
}

module.exports = QosKernel
<|endoftext|>


<commit_before>'use strict';

var gutil = require('gulp-util');
var chalk = require('chalk');

/**
 * Returns the fail reporter
 *
 * The fail reporter raises an error on files stream if PHP linter fails
 *
 * @returns {Function}
 */
module.exports = function(file) {
  var report = file.phplintReport || {};

  if (report.error) {
    var message = report.message;

    if (report.rule) {
      message = report.rule + ' ' + chalk.magenta(file.path) + ':' +
        chalk.yellow(report.line) + ' ' + report.message;
    }

    console.log(message);

  }
};
<commit_msg>Added proper exit code on execution for usage in build scripts<commit_after>'use strict';

var gutil = require('gulp-util');
var chalk = require('chalk');

/**
 * Returns the fail reporter
 *
 * The fail reporter raises an error on files stream if PHP linter fails
 *
 * @returns {Function}
 */
module.exports = function(file) {
  var report = file.phplintReport || {};

  if (report.error) {
    var message = report.message;

    if (report.rule) {
      message = report.rule + ' ' + chalk.magenta(file.path) + ':' +
        chalk.yellow(report.line) + ' ' + report.message;
    }

    console.log(message);
    process.exit(1);
  }
};
<|endoftext|>


<commit_before>/**
 * Created by berthelot on 03/11/14.
 * Based on https://github.com/nicam/js-resize/blob/master/resize.js
 */
'use strict';

angular.module('images-resizer')
    .service('resizeService', ['$q', function($q) {

        var mainCanvas;
        var _this = this;

        var isCanvasSupported = !!(document.createElement('canvas').getContext && document.createElement('canvas').getContext('2d'));

        /**
         * Creates a new image object from the src attributes
         * @param src string src attribute of an img element
         * @returns promise
         */
        this.createImage = function (src) {
            var deferred = $q.defer();
            var img = new Image();

            img.onload = function() {
                deferred.resolve(img);
            };

            img.src = src;

            return deferred.promise;
        };

        this.resizeCanvas = function (cnv, width, height) {
            if(!width || !height) { return cnv; }

            var tmpCanvas = document.createElement('canvas');
            tmpCanvas.width = width;
            tmpCanvas.height = height;
            var cnx = tmpCanvas.getContext('2d');
            cnx.drawImage(cnv, 0, 0, tmpCanvas.width, tmpCanvas.height);
            return tmpCanvas;
        };

        /**
         * Resize an image with a given src attribute
         * @param src string src attribute of an img element
         * @param options json contain all options to resize an image (see all options available directly in th code below)
         * @param cb function callback of the function
         */
        this.resizeImage = function(src, options, cb) {
            //if no callback is specified or canvas is not supported
            if(!isCanvasSupported) {
                cb('Canvas is not supported on your browser', null);
                return;
            }
            if(!cb || !options || !src) {
                cb('Missing argument when calling resizeImage function', null);
                return;
            }

            options = {
                height: options.height ? options.height : options.width ? null: options.size ? null: 1024,
                width: options.width ? options.width : options.height ? null: options.size ? null: 1024,
                size: options.size ? options.size : 500,
                sizeScale: options.size ? options.size : 'ko',
                step: options.step ? options.step : 3
            };

            _this.createImage(src).then(
                function(img) {
                    if (options.height || options.width) {
                        _this.createImage(src).then(
                            function (img) {
                                cb(null, _this.resizeImageWidthHeight(img, options.width, options.height , options.step));
                            },
                            function (err) {
                                cb(err, null);
                            }
                        );
                    }
                    else if (options.size) {
                        //conversion of the size in bytes
                        if (typeof options.sizeScale === 'string') {
                            switch (options.sizeScale.toLowerCase()) {
                                case 'ko':
                                    options.size *= 1024;
                                    break;
                                case 'mo':
                                    options.size *= 1024 * 1024;
                                    break;
                                case 'go':
                                    options.size *= 1024 * 1024 * 1024;
                                    break;
                            }
                        }
                        cb(null, _this.resizeImageBySize(img, options.size, options.step));
                    }
                },
                function(err) {
                    cb(err, null);
                });
        };

        /**
         * Resize image according to the width or the height or both
         * @param image DomImage html image to resize
         * @param width integer desired final image width
         * @param height integer desired final image height
         * @param step integer the number of step to finally have the image to the desired size
         * @returns resized image
         */
        this.resizeImageWidthHeight = function(image, width, height, step) {
            if(!image) { return null; }

            mainCanvas = document.createElement('canvas');

            //Check what width and height the resized image must to be !
            if(!width && ! height){
                width = image.width;
                height = image.height;
            }
            else if(!width && height) {
                width = (height / image.height) * image.width;
            }
            else if(width && !height){
                height = (width/ image.width) * image.height;
            }

            var pixelStepWidth = (image.width === width) || !step ? 0 : (image.width - width)/step;
            var pixelStepHeight = (image.height === height) || !step ? 0 : (image.height - height)/step;
            mainCanvas.width = image.width;
            mainCanvas.height = image.height;

            mainCanvas.getContext('2d').drawImage(image, 0, 0, mainCanvas.width , mainCanvas.height);
            for(var i=1; i<step; i++){
                var newWidth = image.width - (pixelStepWidth * i);
                var newHeight = image.height- (pixelStepHeight * i);
                mainCanvas = this.resizeCanvas(mainCanvas, newWidth, newHeight);
            }
            mainCanvas = this.resizeCanvas(mainCanvas, width, height);

            return mainCanvas.toDataURL('image/jpeg');
        };

        /**
         * Resize image to the approximately absolute size in octet
         * @param image htmlImage the miage to resize
         * @param size number the final size in octet
         * @returns resize image in base64
         */
        this.resizeImageBySize = function (image, size) {
            if(!image){
                return null;
            }
            mainCanvas = document.createElement('canvas');
            mainCanvas.width = image.width;
            mainCanvas.height = image.height;

            mainCanvas.getContext('2d').drawImage(image, 0, 0, mainCanvas.width, mainCanvas.height);
            var tmpResult = mainCanvas.toDataURL('image/jpeg');

            var sizeOfTheImage =  (Math.round(tmpResult.length - 'data:image/jpg;base64,'.length)*3/4);

            while( sizeOfTheImage > size) {
                var canvas = document.createElement('canvas');
                canvas.width = mainCanvas.width /2;
                canvas.height = mainCanvas.height / 2;

                canvas.getContext('2d').drawImage(mainCanvas, 0, 0, canvas.width, canvas.height);

                mainCanvas =  canvas;
                tmpResult = mainCanvas.toDataURL('image/jpeg');
                sizeOfTheImage = (Math.round(tmpResult.length - 'data:image/jpg;base64,'.length)*3/4);
            }

            return tmpResult;
        };

    }]);<commit_msg>We can now resize image by size in octet<commit_after>/**
 * Created by berthelot on 03/11/14.
 * Based on https://github.com/nicam/js-resize/blob/master/resize.js
 */
'use strict';

angular.module('images-resizer')
    .service('resizeService', ['$q', function($q) {

        var mainCanvas;
        var _this = this;

        var isCanvasSupported = !!(document.createElement('canvas').getContext && document.createElement('canvas').getContext('2d'));

        /**
         * Creates a new image object from the src attributes
         * @param src string src attribute of an img element
         * @returns promise
         */
        this.createImage = function (src) {
            var deferred = $q.defer();
            var img = new Image();

            img.onload = function() {
                deferred.resolve(img);
            };

            img.src = src;

            return deferred.promise;
        };

        this.resizeCanvas = function (cnv, width, height) {
            if(!width || !height) { return cnv; }

            var tmpCanvas = document.createElement('canvas');
            tmpCanvas.width = width;
            tmpCanvas.height = height;
            var cnx = tmpCanvas.getContext('2d');
            cnx.drawImage(cnv, 0, 0, tmpCanvas.width, tmpCanvas.height);
            return tmpCanvas;
        };

        /**
         * Resize an image with a given src attribute
         * @param src string src attribute of an img element
         * @param options json contain all options to resize an image (see all options available directly in th code below)
         * @param cb function callback of the function
         */
        this.resizeImage = function(src, options, cb) {
            //if no callback is specified or canvas is not supported
            if(!isCanvasSupported) {
                cb('Canvas is not supported on your browser', null);
                return;
            }
            if(!cb || !options || !src) {
                cb('Missing argument when calling resizeImage function', null);
                return;
            }

            options = {
                height: options.height ? options.height : options.width ? null: options.size ? null: 1024,
                width: options.width ? options.width : options.height ? null: options.size ? null: 1024,
                size: options.size ? options.size : 500,
                sizeScale: options.sizeScale ? options.sizeScale : 'ko',
                step: options.step ? options.step : 3
            };

            _this.createImage(src).then(
                function(img) {
                    if (options.height || options.width) {
                        _this.createImage(src).then(
                            function (img) {
                                cb(null, _this.resizeImageWidthHeight(img, options.width, options.height , options.step));
                            },
                            function (err) {
                                cb(err, null);
                            }
                        );
                    }
                    else if (options.size) {
                        //conversion of the size in bytes
                        if (typeof options.sizeScale === 'string') {
                            switch (options.sizeScale.toLowerCase()) {
                                case 'ko':
                                    options.size *= 1024;
                                    break;
                                case 'mo':
                                    options.size *= 1024 * 1024;
                                    break;
                                case 'go':
                                    options.size *= 1024 * 1024 * 1024;
                                    break;
                            }
                        }
                        cb(null, _this.resizeImageBySize(img, options.size, options.step));
                    }
                },
                function(err) {
                    cb(err, null);
                });
        };

        /**
         * Resize image according to the width or the height or both
         * @param image DomImage html image to resize
         * @param width integer desired final image width
         * @param height integer desired final image height
         * @param step integer the number of step to finally have the image to the desired size
         * @returns resized image
         */
        this.resizeImageWidthHeight = function(image, width, height, step) {
            if(!image) { return null; }

            mainCanvas = document.createElement('canvas');

            //Check what width and height the resized image must to be !
            if(!width && ! height){
                width = image.width;
                height = image.height;
            }
            else if(!width && height) {
                width = (height / image.height) * image.width;
            }
            else if(width && !height){
                height = (width/ image.width) * image.height;
            }

            var pixelStepWidth = (image.width === width) || !step ? 0 : (image.width - width)/step;
            var pixelStepHeight = (image.height === height) || !step ? 0 : (image.height - height)/step;
            mainCanvas.width = image.width;
            mainCanvas.height = image.height;

            mainCanvas.getContext('2d').drawImage(image, 0, 0, mainCanvas.width , mainCanvas.height);
            for(var i=1; i<step; i++){
                var newWidth = image.width - (pixelStepWidth * i);
                var newHeight = image.height- (pixelStepHeight * i);
                mainCanvas = this.resizeCanvas(mainCanvas, newWidth, newHeight);
            }
            mainCanvas = this.resizeCanvas(mainCanvas, width, height);

            return mainCanvas.toDataURL('image/jpeg');
        };

        /**
         * Resize image to the approximately absolute size in octet
         * @param image htmlImage the miage to resize
         * @param size number the final size in octet
         * @returns resize image in base64
         */
        this.resizeImageBySize = function (image, size) {
            if(!image){
                return null;
            }
            mainCanvas = document.createElement('canvas');
            mainCanvas.width = image.width;
            mainCanvas.height = image.height;

            mainCanvas.getContext('2d').drawImage(image, 0, 0, mainCanvas.width, mainCanvas.height);
            var tmpResult = mainCanvas.toDataURL('image/jpeg');
            var result = tmpResult;

            var sizeOfTheImage =  (Math.round(tmpResult.length - 'data:image/jpg;base64,'.length)*3/4);
            //
            var divideStrategy =  sizeOfTheImage / (size * 2) <= 1 ? 0.9 :  sizeOfTheImage / (size * 2.3);

            while( sizeOfTheImage > size) {
                var canvas = document.createElement('canvas');
                canvas.width = mainCanvas.width / divideStrategy;
                canvas.height = mainCanvas.height / divideStrategy;

                canvas.getContext('2d').drawImage(mainCanvas, 0, 0, canvas.width, canvas.height);
                tmpResult = mainCanvas.toDataURL('image/jpeg');
                var sizeOfTheImageTmp = (Math.round(tmpResult.length - 'data:image/jpg;base64,'.length)*3/4);

                if(sizeOfTheImageTmp/size < 0.5) {
                    divideStrategy =  sizeOfTheImage / (size * 2) <= 1 ? 0.9 :  sizeOfTheImage / (size * 2.3);
                }
                else {
                    mainCanvas = canvas;
                    result = tmpResult;
                    sizeOfTheImage = (Math.round(tmpResult.length - 'data:image/jpg;base64,'.length)*3/4);
                }

                mainCanvas =  canvas;
            }

            return result;
        };

    }]);<|endoftext|>


<commit_before>// @todo Rewrite delivery part using priority queue.
// @todo Just make the harvester build a container when none is available.

var gameState = require('game.state');
var utilities = require('utilities');

/**
 * Makes the creep gather resources in the current room.
 */
Creep.prototype.performHarvest = function () {
    var creep = this;
    var source;
    if (creep.memory.fixedSource) {
        source = Game.getObjectById(creep.memory.fixedSource);
        // @todo Just in case, handle source not existing anymore.
    }
    else if (creep.memory.fixedMineralSource) {
        source = Game.getObjectById(creep.memory.fixedMineralSource);
        // @todo Just in case, handle source not existing anymore, or missing extractor.
    }
    else {
        if (!creep.memory.resourceTarget) {
            if (!creep.room.sources || creep.room.sources.length <= 0) {
                return false;
            }

            creep.memory.resourceTarget = creep.room.sources[Math.floor(Math.random() * creep.room.sources.length)].id;
            creep.memory.deliverTarget = null;
        }
        var best = creep.memory.resourceTarget;
        if (!best) {
            return false;
        }
        source = Game.getObjectById(best);
        if (!source) {
            creep.memory.resourceTarget = null;
        }
    }

    if (creep.pos.getRangeTo(source) > 1) {
        creep.moveToRange(source, 1);
    }
    else {
        var result = creep.harvest(source);
    }

    // If there's a link or controller nearby, directly deposit resources.
    if (_.sum(creep.carry) > creep.carryCapacity * 0.5) {
        var target = source.getNearbyContainer();
        if (creep.carry.energy > 0) {
            let link = source.getNearbyLink();
            if (link && link.energy < link.energyCapacity) {
                target = link;
            }
        }
        if (target) {
            if (creep.pos.getRangeTo(target) > 1) {
                creep.moveToRange(target, 1);
            }
            else {
                creep.transferAny(target);
            }
        }
    }

    return true;
};

/**
 * Dumps minerals a harvester creep has gathered.
 */
Creep.prototype.performMineralHarvesterDeliver = function () {
    var creep = this;
    var source = Game.getObjectById(creep.memory.fixedMineralSource);
    var container = source.getNearbyContainer();
    var target;
    // By default, deliver to room's terminal if there's space.
    if (container && _.sum(container.store) + creep.carryCapacity <= container.storeCapacity) {
        target = container;
    }
    else {
        target = this.room.getBestStorageTarget(this.carryCapacity, source.mineralType);
    }

    if (target) {
        if (creep.pos.getRangeTo(target) > 1) {
            creep.moveToRange(target, 1);
        }
        else {
            creep.transferAny(target);
        }
    }
    else {
        // @todo Drop on storage point, I guess? We probably shouldn't be collecting minerals if we have no place to store them.
    }

    return true;
};

/**
 * Dumps resources a harvester creep has gathered.
 */
Creep.prototype.performHarvesterDeliver = function () {
    if (this.memory.fixedMineralSource) {
        return this.performMineralHarvesterDeliver();
    }

    var creep = this;
    var target;

    if (this.memory.fixedSource) {
        var source = Game.getObjectById(creep.memory.fixedSource);
        var targetLink = source.getNearbyLink();
        var targetContainer = source.getNearbyContainer();
        var dropOffSpot = source.getDropoffSpot();

        this.memory.fixedDropoffSpot = dropOffSpot;
        this.memory.fixedTarget = source.memory.targetContainer; // @todo this should not work well...
    }

    if (_.size(creep.room.creepsByRole.transporter) > 0) {
        // Drop off in link or container.
        if (targetLink && targetLink.energy < targetLink.energyCapacity && gameState.getStoredEnergy(creep.room) > 10000) {
            target = targetLink;
        }
        else if (targetContainer && _.sum(targetContainer.store) < targetContainer.storeCapacity) {
            target = targetContainer;
        }
        else if (dropOffSpot) {
            if (true || creep.pos.x == dropOffSpot.x && creep.pos.y == dropOffSpot.y) {
                creep.drop(RESOURCE_ENERGY);
            } else {
                creep.moveTo(dropOffSpot.x, dropOffSpot.y);
            }
            return true;
        }
    }

    if (!target) {
        // @todo Use transporter drop off logic.
        if (!creep.memory.deliverTarget) {
            var targets = creep.room.find(FIND_STRUCTURES, {
                filter: (structure) => {
                    return (structure.structureType == STRUCTURE_EXTENSION ||
                            structure.structureType == STRUCTURE_SPAWN ||
                            structure.structureType == STRUCTURE_TOWER) && structure.energy < structure.energyCapacity;
                }
            });
            if (targets.length <= 0) {
                // Containers get filled when all other structures are full.
                targets = creep.room.find(FIND_STRUCTURES, {
                    filter: (structure) => {
                        return (structure.structureType == STRUCTURE_CONTAINER) && structure.storeCapacity && _.sum(structure.store) < structure.storeCapacity;
                    }
                });
                if (targets.length <= 0) {
                    return false;
                }
            }

            creep.memory.resourceTarget = null;
            creep.memory.deliverTarget = utilities.getClosest(creep, targets);
        }
        var best = creep.memory.deliverTarget;
        if (!best) {
            return false;
        }
        target = Game.getObjectById(best);
        if (!target) {
            creep.memory.deliverTarget = null;
            return false;
        }
    }

    if (source && !targetContainer && creep.pos.getRangeTo(source) <= 1) {
        // Check if there is a container construction site nearby and help build it.
        let sites = source.pos.findInRange(FIND_CONSTRUCTION_SITES, 3, {
            filter: (site) => site.structureType == STRUCTURE_CONTAINER
        });

        if (sites.length > 0) {
            this.buildTarget(sites[0]);
            return true;
        }
    }

    if (creep.pos.getRangeTo(target) > 1) {
        creep.moveToRange(target, 1);
    }
    else {
        creep.transfer(target, RESOURCE_ENERGY);
    }

    if (target.energy >= target.energyCapacity) {
        creep.memory.deliverTarget = null;
    }
    if (target.store && _.sum(target.store) >= target.storeCapacity) {
        if (creep.memory.fixedTarget && target.id == creep.memory.fixedTarget) {
            // Container is full, drop energy instead.
            if (false && creep.memory.fixedDropoffSpot) {
                if (creep.pos.x == creep.memory.fixedDropoffSpot.x && creep.pos.y == creep.memory.fixedDropoffSpot.y) {
                    creep.drop(RESOURCE_ENERGY);
                } else {
                    creep.moveTo(creep.memory.fixedDropoffSpot.x, creep.memory.fixedDropoffSpot.y);
                }
            }
            else {
                // Drop on the spot, I guess.
                creep.drop(RESOURCE_ENERGY);
            }
        }
        else {
            creep.memory.deliverTarget = null;
        }
    }
    return true;
};

/**
 * Puts this creep into or out of harvesting mode.
 */
Creep.prototype.setHarvesterState = function (harvesting) {
    this.memory.harvesting = harvesting;
    delete this.memory.resourceTarget;
    delete this.memory.tempRole;
};

/**
 * Makes a creep behave like a harvester.
 */
Creep.prototype.runHarvesterLogic = function () {
    if (!this.memory.harvesting && _.sum(this.carry) <= 0) {
        this.setHarvesterState(true);
    }
    else if (this.memory.harvesting && _.sum(this.carry) >= this.carryCapacity) {
        this.setHarvesterState(false);
    }

    if (this.memory.harvesting) {
        return this.performHarvest();
    }
    else {
        return this.performHarvesterDeliver();
    }
};
<commit_msg>If the source is empty, pickup any dropped energy.<commit_after>// @todo Rewrite delivery part using priority queue.
// @todo Just make the harvester build a container when none is available.

var gameState = require('game.state');
var utilities = require('utilities');

/**
 * Makes the creep gather resources in the current room.
 */
Creep.prototype.performHarvest = function () {
    var creep = this;
    var source;
    if (creep.memory.fixedSource) {
        source = Game.getObjectById(creep.memory.fixedSource);
        // @todo Just in case, handle source not existing anymore.
    }
    else if (creep.memory.fixedMineralSource) {
        source = Game.getObjectById(creep.memory.fixedMineralSource);
        // @todo Just in case, handle source not existing anymore, or missing extractor.
    }
    else {
        if (!creep.memory.resourceTarget) {
            if (!creep.room.sources || creep.room.sources.length <= 0) {
                return false;
            }

            creep.memory.resourceTarget = creep.room.sources[Math.floor(Math.random() * creep.room.sources.length)].id;
            creep.memory.deliverTarget = null;
        }
        var best = creep.memory.resourceTarget;
        if (!best) {
            return false;
        }
        source = Game.getObjectById(best);
        if (!source) {
            creep.memory.resourceTarget = null;
        }
    }

    if (creep.pos.getRangeTo(source) > 1) {
        creep.moveToRange(source, 1);
    }
    else {
        var result = creep.harvest(source);
    }

    // If the source is empty, pickup any dropped energy.
    if (source.energy == 0) {
        var resource;
        let resources = creep.pos.findInRange(FIND_DROPPED_ENERGY, 3, {
            filter: (resource) => resource.resourceType == RESOURCE_ENERGY
        });
        if (resources.length > 0) {
            resource = resources[0];
            creep.memory.energyPickupTarget = resource.id;
        }

        if(resource) {
            if (creep.pos.getRangeTo(resource) > 1) {
                creep.moveTo(resource);
                return true;
            }
            else {
                creep.pickup(resource);
            }
            actionTaken = true;
        }
    }

    // If there's a link or controller nearby, directly deposit resources.
    if (_.sum(creep.carry) > creep.carryCapacity * 0.5) {
        var target = source.getNearbyContainer();
        if (creep.carry.energy > 0) {
            let link = source.getNearbyLink();
            if (link && link.energy < link.energyCapacity) {
                target = link;
            }
        }
        if (target) {
            if (creep.pos.getRangeTo(target) > 1) {
                creep.moveToRange(target, 1);
            }
            else {
                creep.transferAny(target);
            }
        }
    }

    return true;
};

/**
 * Dumps minerals a harvester creep has gathered.
 */
Creep.prototype.performMineralHarvesterDeliver = function () {
    var creep = this;
    var source = Game.getObjectById(creep.memory.fixedMineralSource);
    var container = source.getNearbyContainer();
    var target;
    // By default, deliver to room's terminal if there's space.
    if (container && _.sum(container.store) + creep.carryCapacity <= container.storeCapacity) {
        target = container;
    }
    else {
        target = this.room.getBestStorageTarget(this.carryCapacity, source.mineralType);
    }

    if (target) {
        if (creep.pos.getRangeTo(target) > 1) {
            creep.moveToRange(target, 1);
        }
        else {
            creep.transferAny(target);
        }
    }
    else {
        // @todo Drop on storage point, I guess? We probably shouldn't be collecting minerals if we have no place to store them.
    }

    return true;
};

/**
 * Dumps resources a harvester creep has gathered.
 */
Creep.prototype.performHarvesterDeliver = function () {
    if (this.memory.fixedMineralSource) {
        return this.performMineralHarvesterDeliver();
    }

    var creep = this;
    var target;

    if (this.memory.fixedSource) {
        var source = Game.getObjectById(creep.memory.fixedSource);
        var targetLink = source.getNearbyLink();
        var targetContainer = source.getNearbyContainer();
        var dropOffSpot = source.getDropoffSpot();

        this.memory.fixedDropoffSpot = dropOffSpot;
        this.memory.fixedTarget = source.memory.targetContainer; // @todo this should not work well...
    }

    if (_.size(creep.room.creepsByRole.transporter) > 0) {
        // Drop off in link or container.
        if (targetLink && targetLink.energy < targetLink.energyCapacity && gameState.getStoredEnergy(creep.room) > 10000) {
            target = targetLink;
        }
        else if (targetContainer && _.sum(targetContainer.store) < targetContainer.storeCapacity) {
            target = targetContainer;
        }
        else if (dropOffSpot) {
            if (true || creep.pos.x == dropOffSpot.x && creep.pos.y == dropOffSpot.y) {
                creep.drop(RESOURCE_ENERGY);
            } else {
                creep.moveTo(dropOffSpot.x, dropOffSpot.y);
            }
            return true;
        }
    }

    if (!target) {
        // @todo Use transporter drop off logic.
        if (!creep.memory.deliverTarget) {
            var targets = creep.room.find(FIND_STRUCTURES, {
                filter: (structure) => {
                    return (structure.structureType == STRUCTURE_EXTENSION ||
                            structure.structureType == STRUCTURE_SPAWN ||
                            structure.structureType == STRUCTURE_TOWER) && structure.energy < structure.energyCapacity;
                }
            });
            if (targets.length <= 0) {
                // Containers get filled when all other structures are full.
                targets = creep.room.find(FIND_STRUCTURES, {
                    filter: (structure) => {
                        return (structure.structureType == STRUCTURE_CONTAINER) && structure.storeCapacity && _.sum(structure.store) < structure.storeCapacity;
                    }
                });
                if (targets.length <= 0) {
                    return false;
                }
            }

            creep.memory.resourceTarget = null;
            creep.memory.deliverTarget = utilities.getClosest(creep, targets);
        }
        var best = creep.memory.deliverTarget;
        if (!best) {
            return false;
        }
        target = Game.getObjectById(best);
        if (!target) {
            creep.memory.deliverTarget = null;
            return false;
        }
    }

    if (source && !targetContainer && creep.pos.getRangeTo(source) <= 1) {
        // Check if there is a container construction site nearby and help build it.
        let sites = source.pos.findInRange(FIND_CONSTRUCTION_SITES, 3, {
            filter: (site) => site.structureType == STRUCTURE_CONTAINER
        });

        if (sites.length > 0) {
            this.buildTarget(sites[0]);
            return true;
        }
    }

    if (creep.pos.getRangeTo(target) > 1) {
        creep.moveToRange(target, 1);
    }
    else {
        creep.transfer(target, RESOURCE_ENERGY);
    }

    if (target.energy >= target.energyCapacity) {
        creep.memory.deliverTarget = null;
    }
    if (target.store && _.sum(target.store) >= target.storeCapacity) {
        if (creep.memory.fixedTarget && target.id == creep.memory.fixedTarget) {
            // Container is full, drop energy instead.
            if (false && creep.memory.fixedDropoffSpot) {
                if (creep.pos.x == creep.memory.fixedDropoffSpot.x && creep.pos.y == creep.memory.fixedDropoffSpot.y) {
                    creep.drop(RESOURCE_ENERGY);
                } else {
                    creep.moveTo(creep.memory.fixedDropoffSpot.x, creep.memory.fixedDropoffSpot.y);
                }
            }
            else {
                // Drop on the spot, I guess.
                creep.drop(RESOURCE_ENERGY);
            }
        }
        else {
            creep.memory.deliverTarget = null;
        }
    }
    return true;
};

/**
 * Puts this creep into or out of harvesting mode.
 */
Creep.prototype.setHarvesterState = function (harvesting) {
    this.memory.harvesting = harvesting;
    delete this.memory.resourceTarget;
    delete this.memory.tempRole;
};

/**
 * Makes a creep behave like a harvester.
 */
Creep.prototype.runHarvesterLogic = function () {
    if (!this.memory.harvesting && _.sum(this.carry) <= 0) {
        this.setHarvesterState(true);
    }
    else if (this.memory.harvesting && _.sum(this.carry) >= this.carryCapacity) {
        this.setHarvesterState(false);
    }

    if (this.memory.harvesting) {
        return this.performHarvest();
    }
    else {
        return this.performHarvesterDeliver();
    }
};
<|endoftext|>


<commit_before>import Main from '@/view/main'
import parentView from '@/components/parent-view'

/**
 * iview-admin中meta除了原生参数外可配置的参数:
 * meta: {
 *  hideInMenu: (false) 设为true后在左侧菜单不会显示该页面选项
 *  notCache: (false) 设为true后页面不会缓存
 *  access: (null) 可访问该页面的权限数组，当前路由设置的权限会影响子路由
 *  icon: (-) 该页面在左侧菜单、面包屑和标签导航处显示的图标，如果是自定义图标，需要在图标名称前加下划线'_'
 * }
 */

export default [
  {
    path: '/login',
    name: 'login',
    meta: {
      title: 'Login - 登录',
      hideInMenu: true
    },
    component: () => import('@/view/login/login.vue')
  },
  {
    path: '/',
    name: '_home',
    redirect: '/home',
    component: Main,
    meta: {
      hideInMenu: true,
      notCache: true
    },
    children: [
      {
        path: '/home',
        name: 'home',
        meta: {
          hideInMenu: true,
          title: '首页',
          notCache: true
        },
        component: () => import('@/view/single-page/home')
      }
    ]
  },
  {
    path: '',
    name: 'doc',
    meta: {
      title: '文档',
      href: 'https://lison16.github.io/iview-admin-doc/#/',
      icon: 'ios-book'
    }
  },
  {
    path: '/join',
    name: 'join',
    component: Main,
    children: [
      {
        path: 'join_page',
        name: 'join_page',
        meta: {
          icon: '_qq',
          title: 'QQ群'
        },
        component: () => import('@/view/join-page.vue')
      }
    ]
  },
  {
    path: '/components',
    name: 'components',
    meta: {
      icon: 'logo-buffer',
      title: '组件'
    },
    component: Main,
    children: [
      {
        path: 'count_to_page',
        name: 'count_to_page',
        meta: {
          icon: 'md-trending-up',
          title: '数字渐变'
        },
        component: () => import('@/view/components/count-to/count-to.vue')
      },
      {
        path: 'tables_page',
        name: 'tables_page',
        meta: {
          icon: 'md-grid',
          title: '多功能表格'
        },
        component: () => import('@/view/components/tables/tables.vue')
      },
      {
        path: 'split_pane_page',
        name: 'split_pane_page',
        meta: {
          icon: 'md-pause',
          title: '分割窗口'
        },
        component: () => import('@/view/components/split-pane/split-pane.vue')
      },
      {
        path: 'markdown_page',
        name: 'markdown_page',
        meta: {
          icon: 'logo-markdown',
          title: 'Markdown编辑器'
        },
        component: () => import('@/view/components/markdown/markdown.vue')
      },
      {
        path: 'editor_page',
        name: 'editor_page',
        meta: {
          icon: 'ios-create',
          title: '富文本编辑器'
        },
        component: () => import('@/view/components/editor/editor.vue')
      },
      {
        path: 'icons_page',
        name: 'icons_page',
        meta: {
          icon: '_bear',
          title: '自定义图标'
        },
        component: () => import('@/view/components/icons/icons.vue')
      }
    ]
  },
  {
    path: '/update',
    name: 'update',
    meta: {
      icon: 'md-cloud-upload',
      title: '数据上传'
    },
    component: Main,
    children: [
      {
        path: 'update_table_page',
        name: 'update_table_page',
        meta: {
          icon: 'ios-document',
          title: '上传Csv'
        },
        component: () => import('@/view/update/update-table.vue')
      },
      {
        path: 'update_paste_page',
        name: 'update_paste_page',
        meta: {
          icon: 'md-clipboard',
          title: '粘贴表格数据'
        },
        component: () => import('@/view/update/update-paste.vue')
      }
    ]
  },
  {
    path: '/tools_methods',
    name: 'tools_methods',
    meta: {
      hide: true
    },
    component: Main,
    children: [
      {
        path: 'tools_methods_page',
        name: 'tools_methods_page',
        meta: {
          icon: 'ios-hammer',
          title: '工具方法'
        },
        component: () => import('@/view/tools-methods/tools-methods.vue')
      }
    ]
  },
  {
    path: '/directive',
    name: 'directive',
    meta: {
      hide: true
    },
    component: Main,
    children: [
      {
        path: 'directive_page',
        name: 'directive_page',
        meta: {
          icon: 'ios-navigate',
          title: '指令'
        },
        component: () => import('@/view/directive/directive.vue')
      }
    ]
  },
  {
    path: '/multilevel',
    name: 'multilevel',
    meta: {
      icon: 'md-menu',
      title: '多级菜单'
    },
    component: Main,
    children: [
      {
        path: 'level_2_1',
        name: 'level_2_1',
        meta: {
          icon: 'md-funnel',
          title: '二级-1'
        },
        component: () => import('@/view/multilevel/level-2-1.vue')
      },
      {
        path: 'level_2_2',
        name: 'level_2_2',
        meta: {
          access: ['super_admin'],
          icon: 'md-funnel',
          showAlways: true,
          title: '二级-2'
        },
        component: parentView,
        children: [
          {
            path: 'level_2_2_1',
            name: 'level_2_2_1',
            meta: {
              icon: 'md-funnel',
              title: '三级'
            },
            component: () => import('@/view/multilevel/level-2-2/level-3-1.vue')
          }
        ]
      },
      {
        path: 'level_2_3',
        name: 'level_2_3',
        meta: {
          icon: 'md-funnel',
          title: '二级-3'
        },
        component: () => import('@/view/multilevel/level-2-3.vue')
      },
    ]
  },
  {
    path: '/argu',
    name: 'argu',
    meta: {
      hideInMenu: true
    },
    component: Main,
    children: [
      {
        path: 'params/:id',
        name: 'params',
        meta: {
          icon: 'md-flower',
          title: '动态路由'
        },
        component: () => import('@/view/argu-page/params.vue')
      },
      {
        path: 'query',
        name: 'query',
        meta: {
          icon: 'md-flower',
          title: '带参路由'
        },
        component: () => import('@/view/argu-page/query.vue')
      }
    ]
  },
  {
    path: '/401',
    name: 'error_401',
    meta: {
      hideInMenu: true
    },
    component: () => import('@/view/error-page/401.vue')
  },
  {
    path: '/500',
    name: 'error_500',
    meta: {
      hideInMenu: true
    },
    component: () => import('@/view/error-page/500.vue')
  },
  {
    path: '*',
    name: 'error_404',
    meta: {
      hideInMenu: true
    },
    component: () => import('@/view/error-page/404.vue')
  }
]
<commit_msg>update router.js<commit_after>import Main from '@/view/main'
import parentView from '@/components/parent-view'

/**
 * iview-admin中meta除了原生参数外可配置的参数:
 * meta: {
 *  hideInMenu: (false) 设为true后在左侧菜单不会显示该页面选项
 *  notCache: (false) 设为true后页面不会缓存
 *  access: (null) 可访问该页面的权限数组，当前路由设置的权限会影响子路由
 *  icon: (-) 该页面在左侧菜单、面包屑和标签导航处显示的图标，如果是自定义图标，需要在图标名称前加下划线'_'
 * }
 */

export default [
  {
    path: '/login',
    name: 'login',
    meta: {
      title: 'Login - 登录',
      hideInMenu: true
    },
    component: () => import('@/view/login/login.vue')
  },
  {
    path: '/',
    name: '_home',
    redirect: '/home',
    component: Main,
    meta: {
      hideInMenu: true,
      notCache: true
    },
    children: [
      {
        path: '/home',
        name: 'home',
        meta: {
          hideInMenu: true,
          title: '首页',
          notCache: true
        },
        component: () => import('@/view/single-page/home')
      }
    ]
  },
  {
    path: '',
    name: 'doc',
    meta: {
      title: '文档',
      href: 'https://lison16.github.io/iview-admin-doc/#/',
      icon: 'ios-book'
    }
  },
  {
    path: '/join',
    name: 'join',
    component: Main,
    children: [
      {
        path: 'join_page',
        name: 'join_page',
        meta: {
          icon: '_qq',
          title: 'QQ群'
        },
        component: () => import('@/view/join-page.vue')
      }
    ]
  },
  {
    path: '/components',
    name: 'components',
    meta: {
      icon: 'logo-buffer',
      title: '组件'
    },
    component: Main,
    children: [
      {
        path: 'count_to_page',
        name: 'count_to_page',
        meta: {
          icon: 'md-trending-up',
          title: '数字渐变'
        },
        component: () => import('@/view/components/count-to/count-to.vue')
      },
      {
        path: 'tables_page',
        name: 'tables_page',
        meta: {
          icon: 'md-grid',
          title: '多功能表格'
        },
        component: () => import('@/view/components/tables/tables.vue')
      },
      {
        path: 'split_pane_page',
        name: 'split_pane_page',
        meta: {
          icon: 'md-pause',
          title: '分割窗口'
        },
        component: () => import('@/view/components/split-pane/split-pane.vue')
      },
      {
        path: 'markdown_page',
        name: 'markdown_page',
        meta: {
          icon: 'logo-markdown',
          title: 'Markdown编辑器'
        },
        component: () => import('@/view/components/markdown/markdown.vue')
      },
      {
        path: 'editor_page',
        name: 'editor_page',
        meta: {
          icon: 'ios-create',
          title: '富文本编辑器'
        },
        component: () => import('@/view/components/editor/editor.vue')
      },
      {
        path: 'icons_page',
        name: 'icons_page',
        meta: {
          icon: '_bear',
          title: '自定义图标'
        },
        component: () => import('@/view/components/icons/icons.vue')
      }
    ]
  },
  {
    path: '/update',
    name: 'update',
    meta: {
      icon: 'md-cloud-upload',
      title: '数据上传'
    },
    component: Main,
    children: [
      {
        path: 'update_table_page',
        name: 'update_table_page',
        meta: {
          icon: 'ios-document',
          title: '上传Csv'
        },
        component: () => import('@/view/update/update-table.vue')
      },
      {
        path: 'update_paste_page',
        name: 'update_paste_page',
        meta: {
          icon: 'md-clipboard',
          title: '粘贴表格数据'
        },
        component: () => import('@/view/update/update-paste.vue')
      }
    ]
  },
  {
    path: '/excel',
    name: 'excel',
    meta: {
      icon: 'ios-stats',
      title: 'EXCEL导入导出'
    },
    component: Main,
    children: [
      {
        path: 'upload-excel',
        name: 'upload-excel',
        meta: {
          icon: 'md-add',
          title: '导入EXCEL'
        },
        component: () => import('@/view/excel/upload-excel.vue')
      },
      {
        path: 'export-excel',
        name: 'export-excel',
        meta: {
          icon: 'md-download',
          title: '导出EXCEL'
        },
        component: () => import('@/view/excel/export-excel.vue')
      }
    ]
  },
  {
    path: '/tools_methods',
    name: 'tools_methods',
    meta: {
      hide: true
    },
    component: Main,
    children: [
      {
        path: 'tools_methods_page',
        name: 'tools_methods_page',
        meta: {
          icon: 'ios-hammer',
          title: '工具方法'
        },
        component: () => import('@/view/tools-methods/tools-methods.vue')
      }
    ]
  },
  {
    path: '/directive',
    name: 'directive',
    meta: {
      hide: true
    },
    component: Main,
    children: [
      {
        path: 'directive_page',
        name: 'directive_page',
        meta: {
          icon: 'ios-navigate',
          title: '指令'
        },
        component: () => import('@/view/directive/directive.vue')
      }
    ]
  },
  {
    path: '/multilevel',
    name: 'multilevel',
    meta: {
      icon: 'md-menu',
      title: '多级菜单'
    },
    component: Main,
    children: [
      {
        path: 'level_2_1',
        name: 'level_2_1',
        meta: {
          icon: 'md-funnel',
          title: '二级-1'
        },
        component: () => import('@/view/multilevel/level-2-1.vue')
      },
      {
        path: 'level_2_2',
        name: 'level_2_2',
        meta: {
          access: ['super_admin'],
          icon: 'md-funnel',
          showAlways: true,
          title: '二级-2'
        },
        component: parentView,
        children: [
          {
            path: 'level_2_2_1',
            name: 'level_2_2_1',
            meta: {
              icon: 'md-funnel',
              title: '三级'
            },
            component: () => import('@/view/multilevel/level-2-2/level-3-1.vue')
          }
        ]
      },
      {
        path: 'level_2_3',
        name: 'level_2_3',
        meta: {
          icon: 'md-funnel',
          title: '二级-3'
        },
        component: () => import('@/view/multilevel/level-2-3.vue')
      },
    ]
  },
  {
    path: '/argu',
    name: 'argu',
    meta: {
      hideInMenu: true
    },
    component: Main,
    children: [
      {
        path: 'params/:id',
        name: 'params',
        meta: {
          icon: 'md-flower',
          title: '动态路由'
        },
        component: () => import('@/view/argu-page/params.vue')
      },
      {
        path: 'query',
        name: 'query',
        meta: {
          icon: 'md-flower',
          title: '带参路由'
        },
        component: () => import('@/view/argu-page/query.vue')
      }
    ]
  },
  {
    path: '/401',
    name: 'error_401',
    meta: {
      hideInMenu: true
    },
    component: () => import('@/view/error-page/401.vue')
  },
  {
    path: '/500',
    name: 'error_500',
    meta: {
      hideInMenu: true
    },
    component: () => import('@/view/error-page/500.vue')
  },
  {
    path: '*',
    name: 'error_404',
    meta: {
      hideInMenu: true
    },
    component: () => import('@/view/error-page/404.vue')
  }
]
<|endoftext|>


<commit_before>function reliableMultiply(a, b) {
	for (;;) {
		try {
			return primitiveMultiply(a, b);
		} catch(e) {
			if (!e instanceof MultiplicatorUnitFailure) {
				throw e;
			}
		}
	}
}
<commit_msg>Fix associativity<commit_after>function reliableMultiply(a, b) {
	for (;;) {
		try {
			return primitiveMultiply(a, b);
		} catch(e) {
			if (!(e instanceof MultiplicatorUnitFailure)) {
				throw e;
			}
		}
	}
}
<|endoftext|>


<commit_before>/*global chrome*/
export function init() {
  chrome.runtime.sendMessage({
    from: 'contentScript',
    type: 'loadEvent',
    loadState: 'load'
  });

  window.addEventListener('unload', () => {
    chrome.runtime.sendMessage({
      from: 'contentScript',
      type: 'loadEvent',
      loadState: 'unload'
    });
  });
}

function dispatchMouseEvent(target, ..args) {
  let e = document.createEvent('MouseEvents');
  e.initEvent.apply(e, args);
  target.dispatchEvent(e);
}

export function simulateClick(el) {
  let events = ['mouseover', 'mousedown', 'click', 'mouseup'];
  for (let event of events) {
    dispatchMouseEvent(el[0], event, true, true);
  }
}
<commit_msg>Use older variable arguments syntax<commit_after>/*global chrome*/
export function init() {
  chrome.runtime.sendMessage({
    from: 'contentScript',
    type: 'loadEvent',
    loadState: 'load'
  });

  window.addEventListener('unload', () => {
    chrome.runtime.sendMessage({
      from: 'contentScript',
      type: 'loadEvent',
      loadState: 'unload'
    });
  });
}

function dispatchMouseEvent(target) {
  let e = document.createEvent('MouseEvents');
  e.initEvent.apply(e, Array.prototype.slice.call(arguments, this.length));
  target.dispatchEvent(e);
}

export function simulateClick(el) {
  let events = ['mouseover', 'mousedown', 'click', 'mouseup'];
  for (let event of events) {
    dispatchMouseEvent(el[0], event, true, true);
  }
}
<|endoftext|>


<commit_before>import { Router } from 'express'

<%_ if (generateAuthApi) { _%>
import user from './<%= apiDir %>/user'
import auth from './<%= apiDir %>/auth'
<%_ } _%>
<%_ if (typeof passwordReset !== 'undefined' && passwordReset) { _%>
import passwordReset from './<%= apiDir %>/password-reset'
<%_ } _%>

const router = new Router()

/**
 * @apiDefine master Master access only
 * You must pass `access_token` parameter or a Bearer Token authorization header
 * to access this endpoint.
 */
/**
 * @apiDefine admin Admin access only
 * You must pass `access_token` parameter or a Bearer Token authorization header
 * to access this endpoint.
 */
/**
 * @apiDefine user User access only
 * You must pass `access_token` parameter or a Bearer Token authorization header
 * to access this endpoint.
 */
/**
 * @apiDefine listParams
 * @apiParam {String} [q] Query to search.
 * @apiParam {Number{1..30}} [page=1] Page number.
 * @apiParam {Number{1..100}} [limit=30] Amount of returned items.
 * @apiParam {String[]} [sort=-createdAt] Order of returned items.
 * @apiParam {String[]} [fields] Fields to be returned.
 */

<%_ if (generateAuthApi) { _%>
router.use('/users', user)
router.use('/auth', auth)
<%_ } _%>
<%_ if (typeof passwordReset !== 'undefined' && passwordReset) { _%>
router.use('/password-resets', passwordReset)
<%_ } _%>

export default router
<commit_msg>Remove blank lines<commit_after>import { Router } from 'express'
<%_ if (generateAuthApi) { _%>
import user from './<%= apiDir %>/user'
import auth from './<%= apiDir %>/auth'
<%_ } _%>
<%_ if (typeof passwordReset !== 'undefined' && passwordReset) { _%>
import passwordReset from './<%= apiDir %>/password-reset'
<%_ } _%>

const router = new Router()

/**
 * @apiDefine master Master access only
 * You must pass `access_token` parameter or a Bearer Token authorization header
 * to access this endpoint.
 */
/**
 * @apiDefine admin Admin access only
 * You must pass `access_token` parameter or a Bearer Token authorization header
 * to access this endpoint.
 */
/**
 * @apiDefine user User access only
 * You must pass `access_token` parameter or a Bearer Token authorization header
 * to access this endpoint.
 */
/**
 * @apiDefine listParams
 * @apiParam {String} [q] Query to search.
 * @apiParam {Number{1..30}} [page=1] Page number.
 * @apiParam {Number{1..100}} [limit=30] Amount of returned items.
 * @apiParam {String[]} [sort=-createdAt] Order of returned items.
 * @apiParam {String[]} [fields] Fields to be returned.
 */
<%_ if (generateAuthApi) { _%>
router.use('/users', user)
router.use('/auth', auth)
<%_ } _%>
<%_ if (typeof passwordReset !== 'undefined' && passwordReset) { _%>
router.use('/password-resets', passwordReset)
<%_ } _%>

export default router
<|endoftext|>


<commit_before>// Switch these lines once there are useful utils
// const testUtils = require('./utils');
require('./utils');

const {MaxLimit} = require('../lib/limit');

describe('Limit Service', function () {
    describe('Max Limit', function () {
        it('throws if initialized without a max limit', function () {
            const config = {};

            try {
                const limit = new MaxLimit({name: 'no limits!', config});
                should.fail(limit, 'Should have errored');
            } catch (err) {
                should.exist(err);
                should.exist(err.errorType);
                should.equal(err.errorType, 'IncorrectUsageError');
            }
        });

        it('throws if initialized without a current count query', function () {
            const config = {};

            try {
                const limit = new MaxLimit({name: 'no accountability!', config});
                should.fail(limit, 'Should have errored');
            } catch (err) {
                should.exist(err);
                should.exist(err.errorType);
                should.equal(err.errorType, 'IncorrectUsageError');
            }
        });

        it('throws if would go over the limit', async function () {
            const config = {
                max: 1,
                currentCountQuery: () => 1
            };
            const limit = new MaxLimit({name: 'maxy', config});

            try {
                await limit.errorIfWouldGoOverLimit();
                should.fail(limit, 'Should have errored');
            } catch (err) {
                should.exist(err);

                should.exist(err.errorType);
                should.equal(err.errorType, 'HostLimitError');

                should.exist(err.errorDetails);
                should.equal(err.errorDetails.name, 'maxy');

                should.exist(err.message);
                should.equal(err.message, 'This action would exceed the maxy limit on your current plan.');
            }
        });

        it('passes if does not go over the limit', async function () {
            const config = {
                max: 2,
                currentCountQuery: () => 1
            };

            const limit = new MaxLimit({name: 'maxy', config});

            await limit.errorIfWouldGoOverLimit();
        });

        it('ignores default configured max limit when it is passed explicitly', async function () {
            const config = {
                max: 10,
                currentCountQuery: () => 10
            };

            const limit = new MaxLimit({name: 'maxy', config});

            // should pass as the limit is overridden to 10 + 1 = 11
            await limit.errorIfWouldGoOverLimit({max: 11});

            try {
                // should fail because limit is overridden to 10 + 1 < 1
                await limit.errorIfWouldGoOverLimit({max: 1});
                should.fail(limit, 'Should have errored');
            } catch (err) {
                should.exist(err);

                should.exist(err.errorType);
                should.equal(err.errorType, 'HostLimitError');

                should.exist(err.errorDetails);
                should.equal(err.errorDetails.name, 'maxy');

                should.exist(err.message);
                should.equal(err.message, 'This action would exceed the maxy limit on your current plan.');
            }
        });
    });
});
<commit_msg>Grouped MaxLimit test cases into describe groups<commit_after>// Switch these lines once there are useful utils
// const testUtils = require('./utils');
require('./utils');

const {MaxLimit} = require('../lib/limit');

describe('Limit Service', function () {
    describe('Max Limit', function () {
        describe('Constructor', function () {
            it('throws if initialized without a max limit', function () {
                const config = {};

                try {
                    const limit = new MaxLimit({name: 'no limits!', config});
                    should.fail(limit, 'Should have errored');
                } catch (err) {
                    should.exist(err);
                    should.exist(err.errorType);
                    should.equal(err.errorType, 'IncorrectUsageError');
                }
            });

            it('throws if initialized without a current count query', function () {
                const config = {};

                try {
                    const limit = new MaxLimit({name: 'no accountability!', config});
                    should.fail(limit, 'Should have errored');
                } catch (err) {
                    should.exist(err);
                    should.exist(err.errorType);
                    should.equal(err.errorType, 'IncorrectUsageError');
                }
            });
        });

        describe('Would go over limit', function () {
            it('throws if would go over the limit', async function () {
                const config = {
                    max: 1,
                    currentCountQuery: () => 1
                };
                const limit = new MaxLimit({name: 'maxy', config});

                try {
                    await limit.errorIfWouldGoOverLimit();
                    should.fail(limit, 'Should have errored');
                } catch (err) {
                    should.exist(err);

                    should.exist(err.errorType);
                    should.equal(err.errorType, 'HostLimitError');

                    should.exist(err.errorDetails);
                    should.equal(err.errorDetails.name, 'maxy');

                    should.exist(err.message);
                    should.equal(err.message, 'This action would exceed the maxy limit on your current plan.');
                }
            });

            it('passes if does not go over the limit', async function () {
                const config = {
                    max: 2,
                    currentCountQuery: () => 1
                };

                const limit = new MaxLimit({name: 'maxy', config});

                await limit.errorIfWouldGoOverLimit();
            });

            it('ignores default configured max limit when it is passed explicitly', async function () {
                const config = {
                    max: 10,
                    currentCountQuery: () => 10
                };

                const limit = new MaxLimit({name: 'maxy', config});

                // should pass as the limit is overridden to 10 + 1 = 11
                await limit.errorIfWouldGoOverLimit({max: 11});

                try {
                    // should fail because limit is overridden to 10 + 1 < 1
                    await limit.errorIfWouldGoOverLimit({max: 1});
                    should.fail(limit, 'Should have errored');
                } catch (err) {
                    should.exist(err);

                    should.exist(err.errorType);
                    should.equal(err.errorType, 'HostLimitError');

                    should.exist(err.errorDetails);
                    should.equal(err.errorDetails.name, 'maxy');

                    should.exist(err.message);
                    should.equal(err.message, 'This action would exceed the maxy limit on your current plan.');
                }
            });
        });
    });
});
<|endoftext|>


<commit_before>const moment = require('moment-timezone');
const path = require('path');
const fs = require('fs-extra');
const membersCSV = require('@tryghost/members-csv');
const errors = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');

const urlUtils = require('../../../../shared/url-utils');
const emailTemplate = require('./email-template');

const messages = {
    filenameCollision: 'Filename already exists, please try again.',
    jobAlreadyComplete: 'Job is already complete.'
};

module.exports = class MembersCSVImporter {
    /**
     * @param {Object} config
     * @param {string} config.storagePath - The path to store CSV's in before importing
     * @param {Object} settingsCache - An instance of the Ghost Settings Cache
     * @param {() => Object} getMembersApi
    * @param {Object} ghostMailer - An instance of GhostMailer
     * @param {(string) => boolean} isSet - Method checking if specific feature is enabled
     * @param {({name, at, job, data, offloaded}) => void} addJob - Method registering an async job
     * @param {Object} knex - An instance of the Ghost Database connection
     */
    constructor(config, settingsCache, getMembersApi, ghostMailer, isSet, addJob, knex) {
        this._storagePath = config.storagePath;
        this._settingsCache = settingsCache;
        this._getMembersApi = getMembersApi;
        this._ghostMailer = ghostMailer;
        this._isSet = isSet;
        this._addJob = addJob;
        this._knex = knex;
    }

    /**
     * @typedef {string} JobID
     */

    /**
     * @typedef {Object} Job
     * @prop {string} filename
     * @prop {JobID} id
     * @prop {string} status
     */

    /**
     * Get the Job for a jobCode
     * @param {JobID} jobId
     * @returns {Promise<Job>}
     */
    async getJob(jobId) {
        return {
            id: jobId,
            filename: jobId,
            status: 'pending'
        };
    }

    /**
     * Prepares a CSV file for import
     * - Maps headers based on headerMapping, this allows for a non standard CSV
     *   to be imported, so long as a mapping exists between it and a standard CSV
     * - Stores the CSV to be imported in the storagePath
     * - Creates a MemberImport Job and associated MemberImportBatch's
     *
     * @param {string} inputFilePath - The path to the CSV to prepare
     * @param {Object.<string, string>} headerMapping - An object whos keys are headers in the input CSV and values are the header to replace it with
     * @param {Array<string>} defaultLabels - A list of labels to apply to every member
     *
     * @returns {Promise<{id: JobID, batches: number, metadata: Object.<string, any>}>} - A promise resolving to the id of the MemberImport Job
     */
    async prepare(inputFilePath, headerMapping, defaultLabels) {
        const batchSize = 1;

        const siteTimezone = this._settingsCache.get('timezone');
        const currentTime = moment().tz(siteTimezone).format('YYYY-MM-DD HH:mm:ss.SSS');
        const outputFileName = `Members Import ${currentTime}.csv`;
        const outputFilePath = path.join(this._storagePath, '/', outputFileName);

        const pathExists = await fs.pathExists(outputFilePath);

        if (pathExists) {
            throw new errors.DataImportError(tpl(messages.filenameCollision));
        }

        const rows = await membersCSV.parse(inputFilePath, headerMapping, defaultLabels);
        const numberOfBatches = Math.ceil(rows.length / batchSize);
        const mappedCSV = membersCSV.unparse(rows);

        const hasStripeData = rows.find(function rowHasStripeData(row) {
            return !!row.stripe_customer_id || !!row.complimentary_plan;
        });

        await fs.writeFile(outputFilePath, mappedCSV);

        return {
            id: outputFilePath,
            batches: numberOfBatches,
            metadata: {
                hasStripeData
            }
        };
    }

    /**
     * Performs an import of a CSV file
     *
     * @param {JobID} id - The id of the job to perform
     */
    async perform(id) {
        const job = await this.getJob(id);

        if (job.status === 'complete') {
            throw new errors.BadRequestError(tpl(messages.jobAlreadyComplete));
        }

        const rows = membersCSV.parse(job.filename);

        const membersApi = await this._getMembersApi();

        const defaultProductPage = await membersApi.productRepository.list({
            limit: 1
        });

        const defaultProduct = defaultProductPage.data[0];

        const result = await rows.reduce(async (resultPromise, row) => {
            const resultAccumulator = await resultPromise;

            const trx = await this._knex.transaction();
            const options = {
                transacting: trx
            };

            try {
                const existingMember = await membersApi.members.get({email: row.email}, {
                    ...options,
                    withRelated: ['labels']
                });
                let member;
                if (existingMember) {
                    const existingLabels = existingMember.related('labels') ? existingMember.related('labels').toJSON() : [];
                    member = await membersApi.members.update({
                        ...row,
                        labels: existingLabels.concat(row.labels)
                    }, {
                        ...options,
                        id: existingMember.id
                    });
                } else {
                    member = await membersApi.members.create(row, options);
                }

                if (row.stripe_customer_id) {
                    await membersApi.members.linkStripeCustomer({
                        customer_id: row.stripe_customer_id,
                        member_id: member.id
                    }, options);
                } else if (row.complimentary_plan) {
                    if (!this._isSet('multipleProducts')) {
                        await membersApi.members.setComplimentarySubscription(member, options);
                    } else if (!row.products) {
                        await membersApi.members.update({
                            products: [{id: defaultProduct.id}]
                        }, {
                            ...options,
                            id: member.id
                        });
                    }
                }

                if (this._isSet('multipleProducts')) {
                    if (row.products) {
                        await membersApi.members.update({
                            products: row.products
                        }, {
                            ...options,
                            id: member.id
                        });
                    }
                }

                await trx.commit();
                return {
                    ...resultAccumulator,
                    imported: resultAccumulator.imported + 1
                };
            } catch (error) {
                // The model layer can sometimes throw arrays of errors
                const errorList = [].concat(error);
                const errorMessage = errorList.map(({message}) => message).join(', ');
                await trx.rollback();
                return {
                    ...resultAccumulator,
                    errors: [...resultAccumulator.errors, {
                        ...row,
                        error: errorMessage
                    }]
                };
            }
        }, Promise.resolve({
            imported: 0,
            errors: []
        }));

        return {
            total: result.imported + result.errors.length,
            ...result
        };
    }

    generateCompletionEmail(result, data) {
        const siteUrl = new URL(urlUtils.urlFor('home', null, true));
        const membersUrl = new URL('members', urlUtils.urlFor('admin', null, true));
        if (data.importLabel) {
            membersUrl.searchParams.set('label', data.importLabel.slug);
        }
        return emailTemplate({result, siteUrl, membersUrl, ...data});
    }

    generateErrorCSV(result) {
        const errorsWithFormattedMessages = result.errors.map((row) => {
            const formattedError = row.error
                .replace(
                    'Value in [members.email] cannot be blank.',
                    'Missing email address'
                )
                .replace(
                    'Value in [members.note] exceeds maximum length of 2000 characters.',
                    '"Note" exceeds maximum length of 2000 characters'
                )
                .replace(
                    'Value in [members.subscribed] must be one of true, false, 0 or 1.',
                    'Value in "Subscribed to emails" must be "true" or "false"'
                )
                .replace(
                    'Validation (isEmail) failed for email',
                    'Invalid email address'
                )
                .replace(
                    /No such customer:[^,]*/,
                    'Could not find Stripe customer'
                );

            return {
                ...row,
                error: formattedError
            };
        });
        return membersCSV.unparse(errorsWithFormattedMessages);
    }

    /**
     * Processes CSV file and imports member&label records depending on the size of the imported set
     *
     * @param {Object} config
     * @param {String} config.pathToCSV - path where imported csv with members records is stored
     * @param {Object} config.headerMapping - mapping of CSV headers to member record fields
     * @param {Object} [config.globalLabels] - labels to be applied to whole imported members set
     * @param {Object} config.importLabel -
     * @param {String} config.importLabel.name - label name
     * @param {Object} config.user
     * @param {String} config.user.email - calling user email
     * @param {Object} config.LabelModel - instance of Ghosts Label model
     */
    async process({pathToCSV, headerMapping, globalLabels, importLabel, user, LabelModel}) {
        const job = await this.prepare(pathToCSV, headerMapping, globalLabels);

        if (job.batches <= 500 && !job.metadata.hasStripeData) {
            const result = await this.perform(job.id);
            const importLabelModel = result.imported ? await LabelModel.findOne(importLabel) : null;
            return {
                meta: {
                    stats: {
                        imported: result.imported,
                        invalid: result.errors
                    },
                    import_label: importLabelModel
                }
            };
        } else {
            const emailRecipient = user.email;
            this._addJob({
                job: async () => {
                    const result = await this.perform(job.id);
                    const importLabelModel = result.imported ? await LabelModel.findOne(importLabel) : null;
                    const emailContent = this.generateCompletionEmail(result, {
                        emailRecipient,
                        importLabel: importLabelModel ? importLabelModel.toJSON() : null
                    });
                    const errorCSV = this.generateErrorCSV(result);
                    const emailSubject = result.imported > 0 ? 'Your member import is complete' : 'Your member import was unsuccessful';

                    await this._ghostMailer.send({
                        to: emailRecipient,
                        subject: emailSubject,
                        html: emailContent,
                        forceTextContent: true,
                        attachments: [{
                            filename: `${importLabel.name} - Errors.csv`,
                            contents: errorCSV,
                            contentType: 'text/csv',
                            contentDisposition: 'attachment'
                        }]
                    });
                },
                offloaded: false
            });

            return {};
        }
    }
};
<commit_msg>Refactored url-uitls out of MembersCSVImporter<commit_after>const moment = require('moment-timezone');
const path = require('path');
const fs = require('fs-extra');
const membersCSV = require('@tryghost/members-csv');
const errors = require('@tryghost/errors');
const tpl = require('@tryghost/tpl');

const emailTemplate = require('./email-template');

const messages = {
    filenameCollision: 'Filename already exists, please try again.',
    jobAlreadyComplete: 'Job is already complete.'
};

module.exports = class MembersCSVImporter {
    /**
     * @param {Object} config
     * @param {string} config.storagePath - The path to store CSV's in before importing
     * @param {Object} settingsCache - An instance of the Ghost Settings Cache
     * @param {() => Object} getMembersApi
     * @param {Object} ghostMailer - An instance of GhostMailer
     * @param {(string) => boolean} isSet - Method checking if specific feature is enabled
     * @param {({name, at, job, data, offloaded}) => void} addJob - Method registering an async job
     * @param {Object} knex - An instance of the Ghost Database connection
     * @param {Function} urlFor - function generating urls
     */
    constructor(config, settingsCache, getMembersApi, ghostMailer, isSet, addJob, knex, urlFor) {
        this._storagePath = config.storagePath;
        this._settingsCache = settingsCache;
        this._getMembersApi = getMembersApi;
        this._ghostMailer = ghostMailer;
        this._isSet = isSet;
        this._addJob = addJob;
        this._knex = knex;
        this._urlFor = urlFor;
    }

    /**
     * @typedef {string} JobID
     */

    /**
     * @typedef {Object} Job
     * @prop {string} filename
     * @prop {JobID} id
     * @prop {string} status
     */

    /**
     * Get the Job for a jobCode
     * @param {JobID} jobId
     * @returns {Promise<Job>}
     */
    async getJob(jobId) {
        return {
            id: jobId,
            filename: jobId,
            status: 'pending'
        };
    }

    /**
     * Prepares a CSV file for import
     * - Maps headers based on headerMapping, this allows for a non standard CSV
     *   to be imported, so long as a mapping exists between it and a standard CSV
     * - Stores the CSV to be imported in the storagePath
     * - Creates a MemberImport Job and associated MemberImportBatch's
     *
     * @param {string} inputFilePath - The path to the CSV to prepare
     * @param {Object.<string, string>} headerMapping - An object whos keys are headers in the input CSV and values are the header to replace it with
     * @param {Array<string>} defaultLabels - A list of labels to apply to every member
     *
     * @returns {Promise<{id: JobID, batches: number, metadata: Object.<string, any>}>} - A promise resolving to the id of the MemberImport Job
     */
    async prepare(inputFilePath, headerMapping, defaultLabels) {
        const batchSize = 1;

        const siteTimezone = this._settingsCache.get('timezone');
        const currentTime = moment().tz(siteTimezone).format('YYYY-MM-DD HH:mm:ss.SSS');
        const outputFileName = `Members Import ${currentTime}.csv`;
        const outputFilePath = path.join(this._storagePath, '/', outputFileName);

        const pathExists = await fs.pathExists(outputFilePath);

        if (pathExists) {
            throw new errors.DataImportError(tpl(messages.filenameCollision));
        }

        const rows = await membersCSV.parse(inputFilePath, headerMapping, defaultLabels);
        const numberOfBatches = Math.ceil(rows.length / batchSize);
        const mappedCSV = membersCSV.unparse(rows);

        const hasStripeData = rows.find(function rowHasStripeData(row) {
            return !!row.stripe_customer_id || !!row.complimentary_plan;
        });

        await fs.writeFile(outputFilePath, mappedCSV);

        return {
            id: outputFilePath,
            batches: numberOfBatches,
            metadata: {
                hasStripeData
            }
        };
    }

    /**
     * Performs an import of a CSV file
     *
     * @param {JobID} id - The id of the job to perform
     */
    async perform(id) {
        const job = await this.getJob(id);

        if (job.status === 'complete') {
            throw new errors.BadRequestError(tpl(messages.jobAlreadyComplete));
        }

        const rows = membersCSV.parse(job.filename);

        const membersApi = await this._getMembersApi();

        const defaultProductPage = await membersApi.productRepository.list({
            limit: 1
        });

        const defaultProduct = defaultProductPage.data[0];

        const result = await rows.reduce(async (resultPromise, row) => {
            const resultAccumulator = await resultPromise;

            const trx = await this._knex.transaction();
            const options = {
                transacting: trx
            };

            try {
                const existingMember = await membersApi.members.get({email: row.email}, {
                    ...options,
                    withRelated: ['labels']
                });
                let member;
                if (existingMember) {
                    const existingLabels = existingMember.related('labels') ? existingMember.related('labels').toJSON() : [];
                    member = await membersApi.members.update({
                        ...row,
                        labels: existingLabels.concat(row.labels)
                    }, {
                        ...options,
                        id: existingMember.id
                    });
                } else {
                    member = await membersApi.members.create(row, options);
                }

                if (row.stripe_customer_id) {
                    await membersApi.members.linkStripeCustomer({
                        customer_id: row.stripe_customer_id,
                        member_id: member.id
                    }, options);
                } else if (row.complimentary_plan) {
                    if (!this._isSet('multipleProducts')) {
                        await membersApi.members.setComplimentarySubscription(member, options);
                    } else if (!row.products) {
                        await membersApi.members.update({
                            products: [{id: defaultProduct.id}]
                        }, {
                            ...options,
                            id: member.id
                        });
                    }
                }

                if (this._isSet('multipleProducts')) {
                    if (row.products) {
                        await membersApi.members.update({
                            products: row.products
                        }, {
                            ...options,
                            id: member.id
                        });
                    }
                }

                await trx.commit();
                return {
                    ...resultAccumulator,
                    imported: resultAccumulator.imported + 1
                };
            } catch (error) {
                // The model layer can sometimes throw arrays of errors
                const errorList = [].concat(error);
                const errorMessage = errorList.map(({message}) => message).join(', ');
                await trx.rollback();
                return {
                    ...resultAccumulator,
                    errors: [...resultAccumulator.errors, {
                        ...row,
                        error: errorMessage
                    }]
                };
            }
        }, Promise.resolve({
            imported: 0,
            errors: []
        }));

        return {
            total: result.imported + result.errors.length,
            ...result
        };
    }

    generateCompletionEmail(result, data) {
        const siteUrl = new URL(this._urlFor('home', null, true));
        const membersUrl = new URL('members', this._urlFor('admin', null, true));
        if (data.importLabel) {
            membersUrl.searchParams.set('label', data.importLabel.slug);
        }
        return emailTemplate({result, siteUrl, membersUrl, ...data});
    }

    generateErrorCSV(result) {
        const errorsWithFormattedMessages = result.errors.map((row) => {
            const formattedError = row.error
                .replace(
                    'Value in [members.email] cannot be blank.',
                    'Missing email address'
                )
                .replace(
                    'Value in [members.note] exceeds maximum length of 2000 characters.',
                    '"Note" exceeds maximum length of 2000 characters'
                )
                .replace(
                    'Value in [members.subscribed] must be one of true, false, 0 or 1.',
                    'Value in "Subscribed to emails" must be "true" or "false"'
                )
                .replace(
                    'Validation (isEmail) failed for email',
                    'Invalid email address'
                )
                .replace(
                    /No such customer:[^,]*/,
                    'Could not find Stripe customer'
                );

            return {
                ...row,
                error: formattedError
            };
        });
        return membersCSV.unparse(errorsWithFormattedMessages);
    }

    /**
     * Processes CSV file and imports member&label records depending on the size of the imported set
     *
     * @param {Object} config
     * @param {String} config.pathToCSV - path where imported csv with members records is stored
     * @param {Object} config.headerMapping - mapping of CSV headers to member record fields
     * @param {Object} [config.globalLabels] - labels to be applied to whole imported members set
     * @param {Object} config.importLabel -
     * @param {String} config.importLabel.name - label name
     * @param {Object} config.user
     * @param {String} config.user.email - calling user email
     * @param {Object} config.LabelModel - instance of Ghosts Label model
     */
    async process({pathToCSV, headerMapping, globalLabels, importLabel, user, LabelModel}) {
        const job = await this.prepare(pathToCSV, headerMapping, globalLabels);

        if (job.batches <= 500 && !job.metadata.hasStripeData) {
            const result = await this.perform(job.id);
            const importLabelModel = result.imported ? await LabelModel.findOne(importLabel) : null;
            return {
                meta: {
                    stats: {
                        imported: result.imported,
                        invalid: result.errors
                    },
                    import_label: importLabelModel
                }
            };
        } else {
            const emailRecipient = user.email;
            this._addJob({
                job: async () => {
                    const result = await this.perform(job.id);
                    const importLabelModel = result.imported ? await LabelModel.findOne(importLabel) : null;
                    const emailContent = this.generateCompletionEmail(result, {
                        emailRecipient,
                        importLabel: importLabelModel ? importLabelModel.toJSON() : null
                    });
                    const errorCSV = this.generateErrorCSV(result);
                    const emailSubject = result.imported > 0 ? 'Your member import is complete' : 'Your member import was unsuccessful';

                    await this._ghostMailer.send({
                        to: emailRecipient,
                        subject: emailSubject,
                        html: emailContent,
                        forceTextContent: true,
                        attachments: [{
                            filename: `${importLabel.name} - Errors.csv`,
                            contents: errorCSV,
                            contentType: 'text/csv',
                            contentDisposition: 'attachment'
                        }]
                    });
                },
                offloaded: false
            });

            return {};
        }
    }
};
<|endoftext|>