plugins-core/cron/plugin.ts

164 lines
3.6 KiB
TypeScript

import { logger } from '@squeebot/core/lib/core';
import {
Plugin,
EventListener,
IPlugin,
} from '@squeebot/core/lib/plugin';
import nodeCron, { ScheduledTask } from 'node-cron';
type ExecutorFn = (...args: any[]) => void;
class CronWrapper {
private cronTask: ScheduledTask | null = null;
public id = Math.random().toString(36).slice(2);
public stopped = true;
public destroyed = false;
constructor(
public expression: string,
public taskFn: ExecutorFn,
public origin: IPlugin,
) {}
assert(): void {
if (!nodeCron.validate(this.expression)) {
throw new Error('Invalid cron pattern!');
}
}
start(): void {
if (this.destroyed) {
logger.warn('[cron] Someone tried to start a destroyed task! This could indicate a memory leak!');
return;
}
if (!this.cronTask) {
this.assert();
this.cronTask = nodeCron.schedule(this.expression, () => {
this.taskFn.call(this.origin);
}, {
scheduled: false,
});
}
this.cronTask.start();
this.stopped = false;
}
stop(): void {
if (!this.cronTask) {
return;
}
this.cronTask.stop();
this.stopped = true;
}
destroy(): void {
this.destroyed = true;
if (!this.cronTask) {
return;
}
this.cronTask.destroy();
this.cronTask = null;
}
belongsTo(plugin: IPlugin | string): boolean {
if (typeof plugin === 'string') {
return this.origin.manifest.name === plugin;
}
return plugin === this.origin ||
plugin.manifest.name === this.origin.manifest.name;
}
}
class CronPlugin extends Plugin {
private timers: CronWrapper[] = [];
@EventListener('pluginUnload')
public unloadEventHandler(plugin: string | Plugin): void {
if (plugin === this.name || plugin === this) {
this.timers.forEach((timer) => timer.destroy());
this.timers = [];
this.emit('pluginUnloaded', this);
}
}
@EventListener('pluginUnloaded')
public unloadedEventHandler(plugin: string | Plugin): void {
this.timers.forEach((timer) => {
if (timer.belongsTo(plugin)) {
timer.destroy();
}
});
this.cleanUp();
}
/**
* @returns A list of tasks including id, cron expression and origin plugin.
*/
public getList(): string[][] {
return this.timers.map((timer) => ([
timer.id,
timer.expression,
timer.origin.manifest.name,
timer.destroyed || timer.stopped ? 'stopped' : 'running',
]));
}
/**
* Register a new cron task
* @param plugin Plugin registering this task
* @param expression Cron expression
* @param taskFn Function to execute
* @param autostart Start the scheduler on add
* @returns The task
*/
public registerTimer(
plugin: IPlugin,
expression: string,
taskFn: ExecutorFn,
autostart = true,
): CronWrapper {
const newTimer = new CronWrapper(expression, taskFn, plugin);
newTimer.assert();
if (autostart) {
newTimer.start();
}
this.timers.push(newTimer);
return newTimer;
}
/**
* Remove a scheduled timer by ID or by the object itself.
* Always use this to ensure that memory is properly cleared.
* @param timer Timer or timer ID
*/
public removeTimer(timer: string | CronWrapper): void {
if (typeof timer === 'string') {
const find = this.timers.find((item) => item.id === timer);
if (find) {
find.destroy();
}
} else {
timer.destroy();
}
this.cleanUp();
}
/**
* Remove destroyed timers from the cache
*/
private cleanUp(): void {
this.timers = this.timers.filter((timer) => !timer.destroyed);
}
}
module.exports = CronPlugin;