Home Reference Source Test Repository

src/Scheduler.js

import {difference, getPatternType} from './utils';
import {JobNotFound} from './errors';

const DEFAULT_JOB_MAX_COUNT = 10000;


/**
 * Scheduler
 */
class Scheduler {
  /**
   * Constructor
   *
   * @param {Object} params params
   * @param {Object} params.robot Hubot robot object
   * @param {string} params.storeKey key of Hubot brain
   * @param {Object} params.job Job class
   * @param {number} [params.jobMaxCount=10000] max number of jobs
   */
  constructor({robot, storeKey, job, jobMaxCount = DEFAULT_JOB_MAX_COUNT}) {
    /**
     * Job list
     *
     * @type {Object}
     */
    this.jobs = {};

    /**
     * Hubot robot object
     *
     * @type {Object}
     */
    this.robot = robot;

    /**
     * key of Hubot brain
     *
     * @type {string}
     */
    this.storeKey = storeKey;

    /**
     * Job class
     *
     * @type {Job}
     */
    this.JobCls = job;

    /**
     * Max number of jobs
     *
     * @type {number}
     */
    this.jobMaxCount = jobMaxCount;

    this.robot.brain.on('loaded', () => this.syncBrain());
    this.initBrain();
  }

  /**
   * Initialize Hubot brain
   */
  initBrain() {
    if (!this.robot.brain.get(this.storeKey)) {
      this.robot.brain.set(this.storeKey, {});
    }
  }

  /**
   * Cancel Job
   *
   * @param {number} id Job id
   */
  cancelJob(id) {
    const job = this.jobs[id];
    if (!job) {
      throw new JobNotFound();
    }
    job.cancel();
    delete this.jobs[id];
    delete this.robot.brain.get(this.storeKey)[id];
  }

  /**
   * Create new Job
   *
   * @param {Object} params params
   * @param {string} params.pattern Cron pattern or Date string
   * @param {Object} params.user Target Hubot user
   * @param {Object} params.meta data for using Job
   * @param {number} [params.id] Job id
   * @return {Job}
   */
  createJob({pattern, user, meta, id}) {
    const jobId = id || this.createId();
    const patternType = getPatternType(pattern);
    let job = null;
    if (patternType === 'cron') {
      job = this.createCronJob({pattern, user, meta, id: jobId});
    } else {
      job = this.createDateJob({pattern, user, meta, id: jobId});
    }
    job.start(this.robot);
    this.jobs[jobId] = job;
    this.storeJobToBrain(jobId, job);
    return job;
  }

  /**
   * Create Job id
   *
   * @return {number}
   */
  createId() {
    let id = null;
    while (!id || this.jobs[id]) {
      id = Math.floor(Math.random() * this.jobMaxCount);
    }
    return id;
  }

  /**
   * Create new CronJob
   *
   * @param {Object} params params
   * @param {string} params.pattern Cron pattern
   * @param {Object} params.user Target Hubot user
   * @param {Object} params.meta Data for using Job
   * @param {number} params.id Job id
   * @return {Job}
   */
  createCronJob({pattern, user, meta, id}) {
    return new this.JobCls({id, pattern, user, meta});
  }

  /**
   * Create new DateJob
   *
   * @param {Object} params params
   * @param {string} params.pattern Date string
   * @param {Object} params.user Target Hubot user
   * @param {Object} params.meta Data for using Job
   * @param {number} params.id Job id
   * @return {Job}
   */
  createDateJob({pattern, user, meta, id}) {
    return new this.JobCls({
      id,
      user,
      meta,
      pattern: new Date(pattern),
      cb: () => {
        delete this.jobs[id];
        delete this.robot.brain.get(this.storeKey)[id];
      }
    });
  }

  /**
   * Create new Job from brain
   *
   * @param {number} id Job id
   * @param {Object} data Date for Job
   * @returns {Job}
   */
  createJobFromBrain(id, data) {
    const {pattern, user, meta} = data;
    return this.createJob({pattern, user, meta, id});
  }

  /**
   * Store Job to brain
   *
   * @param {number} id Job id
   * @param {Job} job Job
   */
  storeJobToBrain(id, job) {
    this.robot.brain.get(this.storeKey)[id] = job.serialize();
  }

  /**
   * Sync Jobs from Brain
   */
  syncBrain() {
    this.initBrain();
    const nonCachedJobs = difference(this.robot.brain.get(this.storeKey), this.jobs);
    Object.getOwnPropertyNames(nonCachedJobs).forEach(id => {
      const jobData = nonCachedJobs[id];
      this.createJobFromBrain(id, jobData);
    });
    const nonStoredJobs = difference(this.jobs, this.robot.brain.get(this.storeKey));
    Object.getOwnPropertyNames(nonStoredJobs).forEach(id => {
      const job = nonStoredJobs[id];
      this.storeJobToBrain(id, job);
    });
  }

  /**
   * Update existes Job
   *
   * @param {number} id Job id
   * @param {Object} meta Data for Job
   */
  updateJob(id, meta) {
    const job = this.jobs[id];
    if (!job) {
      throw new JobNotFound();
    }
    job.meta = meta;
    this.robot.brain.get(this.storeKey)[id] = job.serialize();
  }
}

export default Scheduler;