Handle scheduled cron jobs. You can use intervals (seconds) or specific times to trigger jobs, and the module will take care of setting the proper timers. Jobs are added using “job” objects with id, schedule, callback and other options.
class Cron
events = require "./events.coffee"
fs = require "fs"
lodash = require "lodash"
logger = require "./logger.coffee"
moment = require "moment"
path = require "path"
settings = require "./settings.coffee"
utils = require "./utils.coffee"@property [Array] The jobs collection, please do not edit this object manually!
jobs: []Cron constructor.
constructor: ->
@setEvents() if settings.events.enabledBind event listeners.
setEvents: =>
events.on "cron.start", @start
events.on "cron.stop", @stop
events.on "cron.add", @add
events.on "cron.remove", @removeInit the cron manager. If loadOnInit setting is true, the cron.json file will be parsed
and loaded straight away (if there’s one).
init: (options) =>
logger.debug "Cron.init"
@load true if settings.cron.loadOnInitLoad jobs from the cron.json file. If autoStart is true, it will automatically
call the start method after loading.
@param [String] filename Path to the JSON file containing jobs, optional, default is “cron.json”.
@param [Object] options Options to be passed when loading cron jobs.
@option options [String] basePath Sets the base path of modules when requiring them.
@option options [Boolean] autoStart If true, call “start” after loading.
load: (filename, options) =>
logger.debug "Cron.load", filename, optionsSet default options.
options = {} if not options?
options = lodash.defaults options, {autoStart: false, basePath: ""}
if lodash.isBoolean filename
filename = null
options.autoStart = filename
if not filename? or filename is false or filename is ""
filename = "cron.json"
doNotWarn = trueGet full path to the passed json file.
filepath = utils.getFilePath filename
basename = path.basename filepathFound the cron.json file? Read it.
if filepath?
cronJson = fs.readFileSync filepath, {encoding: settings.general.encoding}
cronJson = utils.minifyJson cronJsonIterate jobs, but do not add if job’s enabled is false.
for key, data of cronJson
module = require(options.basePath + key)
for d in data
if not d.enabled? or d.enabled
cb = module[d.callback]
job = d
job.module = key
job.id = key + "." + d.callback
job.callback = cb
@add job
else
logger.debug "Cron.load", filename, key, d.callback, "Enabled is false. Skip!"Start all jobs automatically if autoStart is true.
@start() if options.autoStart
logger.info "Cron.load", "#{basename} loaded."
else if not doNotWarn
logger.warn "Cron.load", "#{basename} not found."Start the specified cron job. If no id is specified, all jobs will be started.
A filter can also be passed as an object. For example to start all jobs for
the module “email”, use start({module: “email”}).
@param [String] idOrFilter The job id or filter, optional (if not specified, start everything).
start: (idOrFilter) =>
if not idOrFilter?
logger.info "Cron.start", "All jobs"
arr = @jobs
if lodash.isString idOrFilter or lodash.isNumber idOrFilter
logger.info "Cron.start", idOrFilter
arr = lodash.find @jobs, {id: idOrFilter.toString()}
else
logger.info "Cron.start", idOrFilter
arr = lodash.find @jobs, idOrFilter
if not arr? or arr.length < 1
logger.debug "Cron.start", "Job #{idOrFilter} does not exist. Abort!"
else
for job in arr
clearTimeout job.timer if job.timer?
setTimer jobStop the specified cron job. If no id is specified, all jobs will be stopped.
A filter can also be passed as an object. For example to stop all jobs for
the module “mymodule”, use stop({module: “mymodule”}).
@param [String] idOrFilter The job id or filter, optional (if not specified, stop everything).
stop: (idOrFilter) =>
if not idOrFilter?
logger.info "Cron.stop", "All jobs"
arr = @jobs
if lodash.isString idOrFilter or lodash.isNumber idOrFilter
logger.info "Cron.stop", idOrFilter
arr = lodash.find @jobs, {id: idOrFilter.toString()}
else
logger.info "Cron.stop", idOrFilter
arr = lodash.find @jobs, idOrFilter
if not arr? or arr.length < 1
logger.debug "Cron.stop", "Job #{idOrFilter} does not exist. Abort!"
else
for job in arr
clearTimeout job.timer if job.timer?
job.timer = nullAdd a scheduled job to the cron, passing an id and job.
You can also pass only the job if it has an id property.
@param [String] id The job ID, optional, overrides job.id in case it has one.
@param [Object] job The job object.
@option job [String] id The job ID, optional.
@option job [Integer, Array] schedule If a number assume it’s the interval in seconds, otherwise a times array.
@option job [Method] callback The callback (job) to be triggered.
@option job [Boolean] once If true, the job will be triggered only once no matter which schedule it has.
@return [Object] Returns {error, job}, where job is the job object and error is the error message (if any).
add: (id, job) =>
logger.debug "Cron.add", id, job
if id? and not job?
job = id
id = nullIf no id is passed, try getting it directly from the job object.
id = job.id if not id?Throw error if no id was provided or callback is invalid.
if not id? or id is ""
errorMsg = "No 'id' was passed. Abort!"
logger.error "Cron.add", errorMsg
return {error: errorMsg}Throw error if job callback is not a valid function.
if not lodash.isFunction job.callback
errorMsg = "The job #{id} callback is not a valid function. Abort!"
logger.error "Cron.add", errorMsg
return {error: errorMsg}Find existing job.
existing = lodash.find @jobs, {id: id}Handle existing jobs.
if existing?
if settings.cron.allowReplacing
clearTimeout existing.timer if existing.timer?
existing.timer = null
else
errorMsg = "Job #{id} already exists and 'allowReplacing' is false. Abort!"
logger.error "Cron.add", errorMsg
return {error: errorMsg}Set startTime and endTime if not set.
job.startTime = moment 0 if not job.startTime?
job.endTime = moment 0 if not job.endTime?Only create the timer if autoStart is not false.
setTimer job if job.autoStart isnt falseAdd to the jobs list.
job.id = id
@jobs.push job
return {job: job}Remove and stop a current job. If job does not exist, a warning will be logged. @param [String] id The job ID.
remove: (id) =>
existing = lodash.find @jobs, {id: id}Job exists?
if not existing?
logger.debug "Cron.remove", "Job #{id} does not exist. Abort!"
return falseClear timer and remove job from array.
clearTimeout existing.timer if existing.timer?
@jobs.splice existingHelper to get the timeout value (ms) to the next job callback.
getTimeout = (job) ->
now = moment()
nextDate = moment()If schedule is not an array, parse it as integer / seconds.
if lodash.isNumber job.schedule or lodash.isString job.schedule
timeout = moment().add("s", job.schedule).valueOf() - now.valueOf()
else
minTime = "99:99:99"
nextTime = "99:99:99"Get the next and minimum times from schedule.
for sc in job.schedule
minTime = sc if sc < minTime
nextTime = sc if sc < nextTime and sc > nextDate.format("HH:mm:ss")If no times were found for today then set for tomorrow, minimum time.
if nextTime is "99:99:99"
nextDate = nextDate.add "d", 1
nextTime = minTimeReturn the timeout.
arr = nextTime.split ":"
dateValue = [nextDate.year(), nextDate.month(), nextDate.date(), parseInt(arr[0]), parseInt(arr[1]), parseInt(arr[2])]
timeout = moment(dateValue).valueOf() - now.valueOf()
return timeoutHelper to prepare and get a job callback function.
getCallback = (job) ->
callback = ->
logger.debug "Cron", "Job #{job.id} trigger."
job.timer = null
job.startTime = moment()
job.endTime = moment()
job.callback jobOnly reset timer if once is not true.
setTimer job if not job.onceReturn generated callback.
return callbackHelper to get a timer / interval based on the defined options.
setTimer = (job) ->
callback = getCallback jobGet the correct schedule / timeout value.
schedule = job.schedule
schedule = moment.duration(schedule).asMilliseconds() if not lodash.isNumber scheduleMake sure timer is not running.
clearTimeout job.timer if job.timer?Set the timeout based on the defined schedule.
timeout = getTimeout job
job.timer = setTimeout callback, timeout
job.nextRun = moment().add "ms", timeout
logger.debug "Cron.setTimer", job.id, timeoutCron.getInstance = ->
@instance = new Cron() if not @instance?
return @instance
module.exports = exports = Cron.getInstance()