Spaces:
Sleeping
Sleeping
| const fs = require('fs'); | |
| const path = require('path'); | |
| const DATA_DIR = path.join(__dirname, 'data'); | |
| const TASKS_FILE = path.join(DATA_DIR, 'tasks.json'); | |
| const CONFIG_FILE = path.join(DATA_DIR, 'config.json'); | |
| // In-memory cache to avoid read-modify-write race conditions | |
| let tasksCache = null; | |
| // Write throttling | |
| let writePending = false; | |
| let writeTimer = null; | |
| function ensureDir() { | |
| if (!fs.existsSync(DATA_DIR)) { | |
| fs.mkdirSync(DATA_DIR, { recursive: true }); | |
| } | |
| } | |
| // Load tasks into memory cache (lazy load) | |
| function loadTasksCache() { | |
| if (tasksCache === null) { | |
| try { | |
| if (fs.existsSync(TASKS_FILE)) { | |
| const content = fs.readFileSync(TASKS_FILE, 'utf-8'); | |
| const data = JSON.parse(content); | |
| tasksCache = data.tasks || {}; | |
| } else { | |
| tasksCache = {}; | |
| } | |
| } catch (e) { | |
| console.error('Error loading tasks:', e.message); | |
| tasksCache = {}; | |
| } | |
| } | |
| return tasksCache; | |
| } | |
| // Throttled write - batch writes within 100ms | |
| function scheduleWrite() { | |
| if (!writePending) { | |
| writePending = true; | |
| if (writeTimer) { | |
| clearTimeout(writeTimer); | |
| } | |
| writeTimer = setTimeout(() => { | |
| writePending = false; | |
| writeTimer = null; | |
| try { | |
| ensureDir(); | |
| fs.writeFileSync(TASKS_FILE, JSON.stringify({ tasks: tasksCache }, null, 2), 'utf-8'); | |
| } catch (e) { | |
| console.error('Write error:', e.message); | |
| } | |
| }, 100); | |
| } | |
| } | |
| // Immediate write (for critical operations) | |
| function flushWrite() { | |
| if (writeTimer) { | |
| clearTimeout(writeTimer); | |
| writeTimer = null; | |
| } | |
| if (tasksCache !== null) { | |
| try { | |
| ensureDir(); | |
| fs.writeFileSync(TASKS_FILE, JSON.stringify({ tasks: tasksCache }, null, 2), 'utf-8'); | |
| } catch (e) { | |
| console.error('Flush write error:', e.message); | |
| } | |
| } | |
| writePending = false; | |
| } | |
| // Read JSON helper for other files | |
| function readJSON(file, defaultValue = {}) { | |
| try { | |
| if (fs.existsSync(file)) { | |
| const content = fs.readFileSync(file, 'utf-8'); | |
| return JSON.parse(content); | |
| } | |
| } catch (e) { | |
| console.error(`Error reading ${file}:`, e.message); | |
| } | |
| return defaultValue; | |
| } | |
| // Task persistence - all operations use in-memory cache | |
| function loadTasks() { | |
| return { tasks: loadTasksCache() }; | |
| } | |
| function saveTasks(tasks) { | |
| tasksCache = tasks; | |
| scheduleWrite(); | |
| } | |
| function getTask(taskId) { | |
| const cache = loadTasksCache(); | |
| return cache[taskId] || null; | |
| } | |
| function saveTask(taskId, taskData) { | |
| const cache = loadTasksCache(); | |
| cache[taskId] = taskData; | |
| scheduleWrite(); | |
| } | |
| function deleteTask(taskId) { | |
| const cache = loadTasksCache(); | |
| delete cache[taskId]; | |
| scheduleWrite(); | |
| } | |
| function getAllTasks() { | |
| return loadTasksCache(); | |
| } | |
| // Config persistence | |
| function loadConfig() { | |
| return readJSON(CONFIG_FILE, {}); | |
| } | |
| function saveConfig(config) { | |
| ensureDir(); | |
| fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8'); | |
| } | |
| // Raw tasks.json operations (for config editor) | |
| function getTasksRaw() { | |
| flushWrite(); // Ensure latest data is written | |
| return readJSON(TASKS_FILE, { tasks: {} }); | |
| } | |
| function saveTasksRaw(content) { | |
| // Parse and validate | |
| const data = JSON.parse(content); | |
| // Update cache and write immediately | |
| tasksCache = data.tasks || {}; | |
| flushWrite(); | |
| } | |
| // Check if any task is running | |
| function hasRunningTasks() { | |
| const tasks = getAllTasks(); | |
| return Object.values(tasks).some(t => t.status === 'running' || t.status === 'paused'); | |
| } | |
| // Check if there's pending data to write | |
| function hasPendingWrite() { | |
| return writePending || writeTimer !== null; | |
| } | |
| // Graceful shutdown - flush all pending data | |
| function shutdown() { | |
| if (writeTimer) { | |
| clearTimeout(writeTimer); | |
| writeTimer = null; | |
| } | |
| if (tasksCache !== null && writePending) { | |
| try { | |
| ensureDir(); | |
| fs.writeFileSync(TASKS_FILE, JSON.stringify({ tasks: tasksCache }, null, 2), 'utf-8'); | |
| console.log('[Storage] Data flushed on shutdown'); | |
| } catch (e) { | |
| console.error('[Storage] Shutdown flush error:', e.message); | |
| } | |
| } | |
| writePending = false; | |
| } | |
| module.exports = { | |
| loadTasks, | |
| saveTasks, | |
| getTask, | |
| saveTask, | |
| deleteTask, | |
| getAllTasks, | |
| loadConfig, | |
| saveConfig, | |
| getTasksRaw, | |
| saveTasksRaw, | |
| hasRunningTasks, | |
| flushTasks: flushWrite, | |
| hasPendingWrite, | |
| shutdown | |
| }; | |