2026-05-20 11:52:18 -04:00
|
|
|
'use strict';
|
|
|
|
|
const chokidar = require('chokidar');
|
|
|
|
|
const path = require('path');
|
|
|
|
|
const { fullScan, processResultDir } = require('./scanner');
|
|
|
|
|
const { resetByFileIdAndDevice } = require('./db');
|
|
|
|
|
const { broadcast } = require('./sse');
|
|
|
|
|
|
|
|
|
|
let targetWatcher = null;
|
|
|
|
|
let resultsWatcher = null;
|
|
|
|
|
|
|
|
|
|
function debounce(fn, ms) {
|
|
|
|
|
let timer;
|
|
|
|
|
return (...args) => {
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
timer = setTimeout(() => fn(...args), ms);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function startWatching(targetDir, resultsDir) {
|
|
|
|
|
stopWatching();
|
|
|
|
|
if (!targetDir || !resultsDir) return;
|
|
|
|
|
|
|
|
|
|
// ── Target dir ────────────────────────────────────────────────────────────
|
|
|
|
|
// Any change to test files (new cases added or removed) triggers a full
|
|
|
|
|
// rescan so the test list stays in sync.
|
|
|
|
|
targetWatcher = chokidar.watch(targetDir, {
|
|
|
|
|
depth: 1,
|
|
|
|
|
ignoreInitial: true,
|
|
|
|
|
awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const debouncedFullScan = debounce(async () => {
|
|
|
|
|
await fullScan(targetDir, resultsDir);
|
|
|
|
|
broadcast({ type: 'update' });
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
|
|
|
|
targetWatcher.on('add', debouncedFullScan);
|
|
|
|
|
targetWatcher.on('unlink', debouncedFullScan);
|
|
|
|
|
|
|
|
|
|
// ── Results dir ───────────────────────────────────────────────────────────
|
|
|
|
|
// depth: 1 covers both immediate result subdirectories (addDir/unlinkDir)
|
|
|
|
|
// and log files inside them (add).
|
|
|
|
|
resultsWatcher = chokidar.watch(resultsDir, {
|
|
|
|
|
depth: 1,
|
|
|
|
|
ignoreInitial: true,
|
|
|
|
|
awaitWriteFinish: { stabilityThreshold: 2000, pollInterval: 200 },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// New result directory → mark test complete
|
|
|
|
|
resultsWatcher.on('addDir', async (dirPath) => {
|
|
|
|
|
if (path.normalize(dirPath) === path.normalize(resultsDir)) return;
|
|
|
|
|
const dirName = path.basename(dirPath);
|
|
|
|
|
await processResultDir(resultsDir, dirName);
|
|
|
|
|
broadcast({ type: 'update' });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// New log file inside a result directory → update timing
|
|
|
|
|
resultsWatcher.on('add', async (filePath) => {
|
|
|
|
|
const dirPath = path.dirname(filePath);
|
|
|
|
|
if (path.normalize(dirPath) === path.normalize(resultsDir)) return;
|
|
|
|
|
const dirName = path.basename(dirPath);
|
|
|
|
|
await processResultDir(resultsDir, dirName);
|
|
|
|
|
broadcast({ type: 'update' });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Result directory deleted → reset test to pending
|
|
|
|
|
resultsWatcher.on('unlinkDir', (dirPath) => {
|
|
|
|
|
if (path.normalize(dirPath) === path.normalize(resultsDir)) return;
|
|
|
|
|
const dirName = path.basename(dirPath);
|
|
|
|
|
const segs = dirName.split(/[_\-]/);
|
2026-05-21 14:53:08 -04:00
|
|
|
const test_id = segs.find(s => /^R\d+[A-Z0-9]+$/i.test(s)) || null;
|
2026-05-20 11:52:18 -04:00
|
|
|
const device = segs.find(s => /^CGW\d+$/i.test(s)) || null;
|
2026-05-21 14:53:08 -04:00
|
|
|
if (test_id) {
|
|
|
|
|
resetByFileIdAndDevice(test_id, device);
|
2026-05-20 11:52:18 -04:00
|
|
|
broadcast({ type: 'update' });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stopWatching() {
|
|
|
|
|
if (targetWatcher) { targetWatcher.close(); targetWatcher = null; }
|
|
|
|
|
if (resultsWatcher) { resultsWatcher.close(); resultsWatcher = null; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = { startWatching, stopWatching };
|