// 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(); } };