Initial commit
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
// node dependencies
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Path to the tasks.json file, which stores all tasks persistently
|
||||
const tasksFile = path.join(__dirname, 'tasks.json');
|
||||
|
||||
module.exports = {
|
||||
// In-memory object holding all tasks
|
||||
/* Each task has the following structure:
|
||||
tasks[taskName] = {
|
||||
active: false, // whether the task is currently running
|
||||
timer: null, // reference to setTimeout for execution
|
||||
nextRun: null, // timestamp of next scheduled execution
|
||||
lastRun: null, // timestamp of last execution
|
||||
error: null, // last error if the task failed
|
||||
oneTime: true/false, // if true, task runs only once
|
||||
intervalInSec: 0 // interval in seconds for repeated execution
|
||||
action: string // name of the JS file in ROOT/tasks
|
||||
}
|
||||
*/
|
||||
tasks: {},
|
||||
|
||||
// Load tasks from tasks.json into memory
|
||||
loadTasks: function() {
|
||||
// Create empty file if missing
|
||||
if (!fs.existsSync(tasksFile)) {
|
||||
fs.writeFileSync(tasksFile, '{}', 'utf-8');
|
||||
}
|
||||
|
||||
// Read and parse JSON
|
||||
const rawData = fs.readFileSync(tasksFile, 'utf-8');
|
||||
const data = JSON.parse(rawData);
|
||||
|
||||
// Load each task into memory
|
||||
for (const name in data) {
|
||||
const taskData = data[name];
|
||||
|
||||
// Ensure timer is reset in memory
|
||||
taskData.timer = null;
|
||||
|
||||
this.tasks[name] = taskData;
|
||||
|
||||
// If task is active, automatically start the timer
|
||||
if (taskData.active) {
|
||||
this._startTimer(name);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Save current in-memory tasks to tasks.json - Removes timer references before saving
|
||||
saveTasks: function() {
|
||||
const dataToSave = {};
|
||||
|
||||
for (const name in this.tasks) {
|
||||
const task = { ...this.tasks[name] };
|
||||
delete task.timer; // timers cannot be persisted
|
||||
dataToSave[name] = task;
|
||||
}
|
||||
|
||||
fs.writeFileSync(tasksFile, JSON.stringify(dataToSave, null, 4), 'utf-8');
|
||||
},
|
||||
|
||||
// Add or update a task in memory and save it to JSON
|
||||
// If the task does not exist, initialize default fields
|
||||
setTask: function(name, data) {
|
||||
// If task does not exist, create default structure
|
||||
if (!this.tasks[name]) {
|
||||
this.tasks[name] = {
|
||||
active: false,
|
||||
timer: null,
|
||||
nextRun: null,
|
||||
lastRun: null,
|
||||
error: null,
|
||||
oneTime: false,
|
||||
intervalInSec: null,
|
||||
action: null
|
||||
};
|
||||
}
|
||||
|
||||
// Merge new data
|
||||
Object.assign(this.tasks[name], data);
|
||||
|
||||
// Stop any running timer before updating
|
||||
if (this.tasks[name].timer) {
|
||||
clearTimeout(this.tasks[name].timer);
|
||||
this.tasks[name].timer = null;
|
||||
}
|
||||
|
||||
// Immediately start the timer if task is active
|
||||
if (this.tasks[name].active) {
|
||||
this._startTimer(name);
|
||||
}
|
||||
|
||||
// Persist to JSON
|
||||
this.saveTasks();
|
||||
},
|
||||
|
||||
// Remove a task completely from memory and JSON
|
||||
removeTask: function(name) {
|
||||
const task = this.tasks[name];
|
||||
|
||||
// Stop running timer if it exists
|
||||
if (task && task.timer) clearTimeout(task.timer);
|
||||
|
||||
// Remove from memory
|
||||
delete this.tasks[name];
|
||||
|
||||
// Persist changes
|
||||
this.saveTasks();
|
||||
},
|
||||
|
||||
// Retrieve a single task by name
|
||||
getTask: function(name) {
|
||||
return this.tasks[name] || null;
|
||||
},
|
||||
|
||||
// Retrieve all tasks in memory
|
||||
getAllTasks: function() {
|
||||
return this.tasks;
|
||||
},
|
||||
|
||||
/*
|
||||
* Internal function to start the timer for a task
|
||||
* - Loads the action from ROOT/tasks
|
||||
* - Executes the function at intervals or once depending on task.oneTime
|
||||
* @param {string} name - Task name
|
||||
*/
|
||||
_startTimer: function(name) {
|
||||
const task = this.tasks[name];
|
||||
|
||||
// Task must exist and have an action defined
|
||||
if (!task || !task.action) return;
|
||||
|
||||
// Build path to task action file
|
||||
const taskPath = path.join(__dirname, '../../../tasks', task.action + '.js');
|
||||
|
||||
let actionFunc;
|
||||
try {
|
||||
// Load the exported function from the task file
|
||||
actionFunc = require(taskPath);
|
||||
|
||||
if (typeof actionFunc !== 'function') {
|
||||
throw new Error('Task action must export a function');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Failed to load action for task "${name}":`, err.message);
|
||||
task.error = err.message;
|
||||
this.saveTasks();
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert interval to milliseconds
|
||||
const intervalMs = task.intervalInSec ? task.intervalInSec * 1000 : 0;
|
||||
|
||||
// Function executed by the timer
|
||||
const executeTask = async () => {
|
||||
task.lastRun = Date.now();
|
||||
|
||||
try {
|
||||
// Call the action function
|
||||
await actionFunc();
|
||||
task.error = null;
|
||||
} catch (err) {
|
||||
task.error = err.message || String(err);
|
||||
}
|
||||
|
||||
if (!task.oneTime && intervalMs && task.active) {
|
||||
// Recurring task: schedule next run
|
||||
task.nextRun = Date.now() + intervalMs;
|
||||
task.timer = setTimeout(executeTask, intervalMs);
|
||||
} else if (task.oneTime) {
|
||||
// One-time task: deactivate after execution
|
||||
task.active = false;
|
||||
task.nextRun = null;
|
||||
task.timer = null;
|
||||
}
|
||||
|
||||
// Save updated task info
|
||||
this.saveTasks();
|
||||
};
|
||||
|
||||
// Activate the task and schedule first execution
|
||||
task.active = true;
|
||||
task.nextRun = Date.now() + intervalMs;
|
||||
task.timer = setTimeout(executeTask, intervalMs);
|
||||
|
||||
// Save memory state
|
||||
this.saveTasks();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user