164 lines
3.6 KiB
TypeScript
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;
|