123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- 'use strict';
- const events = require('events')
- const cronParser = require('cron-parser')
- const CronDate = require('cron-parser/lib/date')
- const sorted = require('sorted-array-functions')
- const { scheduleNextRecurrence, scheduleInvocation, cancelInvocation, RecurrenceRule, sorter, Invocation } = require('./Invocation')
- const { isValidDate } = require('./utils/dateUtils')
- const scheduledJobs = {};
- let anonJobCounter = 0;
- function resolveAnonJobName() {
- const now = new Date()
- if (anonJobCounter === Number.MAX_SAFE_INTEGER) {
- anonJobCounter = 0
- }
- anonJobCounter++
- return `<Anonymous Job ${anonJobCounter} ${now.toISOString()}>`
- }
- function Job(name, job, callback) {
- // setup a private pendingInvocations variable
- this.pendingInvocations = [];
- //setup a private number of invocations variable
- let triggeredJobs = 0;
- // Set scope vars
- const jobName = name && typeof name === 'string' ? name : resolveAnonJobName();
- this.job = name && typeof name === 'function' ? name : job;
- // Make sure callback is actually a callback
- if (this.job === name) {
- // Name wasn't provided and maybe a callback is there
- this.callback = typeof job === 'function' ? job : false;
- } else {
- // Name was provided, and maybe a callback is there
- this.callback = typeof callback === 'function' ? callback : false;
- }
- // task count
- this.running = 0;
- // Check for generator
- if (typeof this.job === 'function' &&
- this.job.prototype &&
- this.job.prototype.next) {
- this.job = function() {
- return this.next().value;
- }.bind(this.job.call(this));
- }
- // define properties
- Object.defineProperty(this, 'name', {
- value: jobName,
- writable: false,
- enumerable: true
- });
- // method that require private access
- this.trackInvocation = function(invocation) {
- // add to our invocation list
- sorted.add(this.pendingInvocations, invocation, sorter);
- return true;
- };
- this.stopTrackingInvocation = function(invocation) {
- const invIdx = this.pendingInvocations.indexOf(invocation);
- if (invIdx > -1) {
- this.pendingInvocations.splice(invIdx, 1);
- return true;
- }
- return false;
- };
- this.triggeredJobs = function() {
- return triggeredJobs;
- };
- this.setTriggeredJobs = function(triggeredJob) {
- triggeredJobs = triggeredJob;
- };
- this.deleteFromSchedule = function() {
- deleteScheduledJob(this.name)
- };
- this.cancel = function(reschedule) {
- reschedule = (typeof reschedule == 'boolean') ? reschedule : false;
- let inv, newInv;
- const newInvs = [];
- for (let j = 0; j < this.pendingInvocations.length; j++) {
- inv = this.pendingInvocations[j];
- cancelInvocation(inv);
- if (reschedule && (inv.recurrenceRule.recurs || inv.recurrenceRule.next)) {
- newInv = scheduleNextRecurrence(inv.recurrenceRule, this, inv.fireDate, inv.endDate);
- if (newInv !== null) {
- newInvs.push(newInv);
- }
- }
- }
- this.pendingInvocations = [];
- for (let k = 0; k < newInvs.length; k++) {
- this.trackInvocation(newInvs[k]);
- }
- // remove from scheduledJobs if reschedule === false
- if (!reschedule) {
- this.deleteFromSchedule()
- }
- return true;
- };
- this.cancelNext = function(reschedule) {
- reschedule = (typeof reschedule == 'boolean') ? reschedule : true;
- if (!this.pendingInvocations.length) {
- return false;
- }
- let newInv;
- const nextInv = this.pendingInvocations.shift();
- cancelInvocation(nextInv);
- if (reschedule && (nextInv.recurrenceRule.recurs || nextInv.recurrenceRule.next)) {
- newInv = scheduleNextRecurrence(nextInv.recurrenceRule, this, nextInv.fireDate, nextInv.endDate);
- if (newInv !== null) {
- this.trackInvocation(newInv);
- }
- }
- return true;
- };
- this.reschedule = function(spec) {
- let inv;
- const invocationsToCancel = this.pendingInvocations.slice();
- for (let j = 0; j < invocationsToCancel.length; j++) {
- inv = invocationsToCancel[j];
- cancelInvocation(inv);
- }
- this.pendingInvocations = [];
- if (this.schedule(spec)) {
- this.setTriggeredJobs(0);
- return true;
- } else {
- this.pendingInvocations = invocationsToCancel;
- return false;
- }
- };
- this.nextInvocation = function() {
- if (!this.pendingInvocations.length) {
- return null;
- }
- return this.pendingInvocations[0].fireDate;
- };
- }
- Object.setPrototypeOf(Job.prototype, events.EventEmitter.prototype);
- Job.prototype.invoke = function(fireDate) {
- this.setTriggeredJobs(this.triggeredJobs() + 1);
- return this.job(fireDate);
- };
- Job.prototype.runOnDate = function(date) {
- return this.schedule(date);
- };
- Job.prototype.schedule = function(spec) {
- const self = this;
- let success = false;
- let inv;
- let start;
- let end;
- let tz;
- // save passed-in value before 'spec' is replaced
- if (typeof spec === 'object' && 'tz' in spec) {
- tz = spec.tz;
- }
- if (typeof spec === 'object' && spec.rule) {
- start = spec.start || undefined;
- end = spec.end || undefined;
- spec = spec.rule;
- if (start) {
- if (!(start instanceof Date)) {
- start = new Date(start);
- }
- start = new CronDate(start, tz);
- if (!isValidDate(start) || start.getTime() < Date.now()) {
- start = undefined;
- }
- }
- if (end && !(end instanceof Date) && !isValidDate(end = new Date(end))) {
- end = undefined;
- }
- if (end) {
- end = new CronDate(end, tz);
- }
- }
- try {
- const res = cronParser.parseExpression(spec, {currentDate: start, tz: tz});
- inv = scheduleNextRecurrence(res, self, start, end);
- if (inv !== null) {
- success = self.trackInvocation(inv);
- }
- } catch (err) {
- const type = typeof spec;
- if ((type === 'string') || (type === 'number')) {
- spec = new Date(spec);
- }
- if ((spec instanceof Date) && (isValidDate(spec))) {
- spec = new CronDate(spec);
- self.isOneTimeJob = true;
- if (spec.getTime() >= Date.now()) {
- inv = new Invocation(self, spec);
- scheduleInvocation(inv);
- success = self.trackInvocation(inv);
- }
- } else if (type === 'object') {
- self.isOneTimeJob = false;
- if (!(spec instanceof RecurrenceRule)) {
- const r = new RecurrenceRule();
- if ('year' in spec) {
- r.year = spec.year;
- }
- if ('month' in spec) {
- r.month = spec.month;
- }
- if ('date' in spec) {
- r.date = spec.date;
- }
- if ('dayOfWeek' in spec) {
- r.dayOfWeek = spec.dayOfWeek;
- }
- if ('hour' in spec) {
- r.hour = spec.hour;
- }
- if ('minute' in spec) {
- r.minute = spec.minute;
- }
- if ('second' in spec) {
- r.second = spec.second;
- }
- spec = r;
- }
- spec.tz = tz;
- inv = scheduleNextRecurrence(spec, self, start, end);
- if (inv !== null) {
- success = self.trackInvocation(inv);
- }
- }
- }
- scheduledJobs[this.name] = this;
- return success;
- };
- function deleteScheduledJob(name) {
- if (name) {
- delete scheduledJobs[name];
- }
- }
- module.exports = {
- Job,
- deleteScheduledJob,
- scheduledJobs
- }
|