'use strict'; const fs = require('fs'); const path = require('path'); const readline = require('readline'); const { parseFilename, parseTimestamp, parseElapsedTime } = require('./parser'); const { upsertTest, markCompleted, clearTests } = require('./db'); /** * Full rebuild: clears the DB, walks target dir to build the test list, * then walks results dir to mark completions. */ async function fullScan(targetDir, resultsDir) { clearTests(); if (!targetDir || !resultsDir) return; console.log(`[scanner] target dir : ${targetDir}`); console.log(`[scanner] results dir: ${resultsDir}`); // ── 1. Walk target directory ────────────────────────────────────────────── let parentEntries; try { parentEntries = fs.readdirSync(targetDir, { withFileTypes: true }).filter(d => d.isDirectory()); } catch (e) { console.error('[scanner] Cannot read target dir:', e.message); return; } console.log(`[scanner] subdirectories found: ${parentEntries.length}`); for (const parentEntry of parentEntries) { const parentDirPath = path.join(targetDir, parentEntry.name); let files; try { files = fs.readdirSync(parentDirPath, { withFileTypes: true }) .filter(f => f.isFile() && f.name.startsWith('TC_WIFI_') && f.name.endsWith('.ini')); } catch (e) { console.error(`[scanner] Cannot read parent dir ${parentEntry.name}:`, e.message); continue; } console.log(`[scanner] ${parentEntry.name} → ${files.length} TC_WIFI_*.ini file(s)`); for (const file of files) { const parsed = parseFilename(file.name, parentEntry.name); if (!parsed.file_id) { console.log(`[scanner] skip (no file_id): ${parentEntry.name}/${file.name}`); continue; } console.log(`[scanner] found: ${parentEntry.name}/${file.name} → file_id=${parsed.file_id}`); upsertTest({ id: parsed.id, file_id: parsed.file_id, parent_dir: parentEntry.name, filename: file.name, interference: parsed.interference, device: parsed.device, rotation: parsed.rotation, test_point: parsed.test_point, station: parsed.station, band: parsed.band, channel: parsed.channel, bandwidth: parsed.bandwidth, rssi: parsed.rssi, direction: parsed.direction, }); } } // ── 2. Walk results directory ───────────────────────────────────────────── await scanResults(resultsDir); } /** * Scan the results directory and mark all matched tests as completed. */ async function scanResults(resultsDir) { let entries; try { entries = fs.readdirSync(resultsDir, { withFileTypes: true }).filter(d => d.isDirectory()); } catch (e) { console.error('[scanner] Cannot read results dir:', e.message); return; } console.log(`[scanner] results: ${entries.length} result dir(s) found`); for (const entry of entries) { await processResultDir(resultsDir, entry.name); } } /** * Process one result directory: extract file_id, find the latest matching * log file, parse its elapsed-time line, and mark the test as completed. */ async function processResultDir(resultsDir, resultDirName) { const segments = resultDirName.split(/[_\-]/); const file_id = segments.find(s => /^R\d+[A-Z0-9]+$/i.test(s)) || null; const device = segments.find(s => /^CGW\d+$/i.test(s)) || null; console.log(`[scanner] result dir: ${resultDirName} → file_id=${file_id} device=${device}`); if (!file_id) return; const resultDirPath = path.join(resultsDir, resultDirName); let logFiles; try { logFiles = fs.readdirSync(resultDirPath, { withFileTypes: true }) .filter(f => f.isFile() && f.name.endsWith('.txt') && f.name.includes(file_id)) .map(f => f.name); } catch { return; } if (logFiles.length === 0) { // Result directory exists but log not written yet — mark complete without timing console.log(`[scanner] completed (no log yet): ${file_id}`); markCompleted(file_id, device, null, null); return; } // For re-runs, the latest log file has the latest timestamp in its name const latestLog = logFiles.sort().at(-1); const logPath = path.join(resultDirPath, latestLog); const completed_at = parseTimestamp(latestLog); const duration_seconds = await extractDuration(logPath, file_id); console.log(`[scanner] completed: ${file_id} device=${device} duration=${duration_seconds}s at=${completed_at}`); markCompleted(file_id, device, completed_at, duration_seconds); } /** * Stream-read a log file and return the elapsed time in seconds, * or null if the expected line is not found. */ function extractDuration(logFilePath) { console.log(`[scanner] extracting duration from log: ${logFilePath}`); const REGEX = /\[.*?INFO\]\s+Elapsed time\s*:\s*([\d]+:[\d]{2}:[\d]{2}\.[\d]+)/; return new Promise((resolve) => { let stream; try { stream = fs.createReadStream(logFilePath, { encoding: 'utf8' }); } catch { return resolve(null); } const rl = readline.createInterface({ input: stream, crlfDelay: Infinity }); let done = false; function finish(value) { if (done) return; done = true; resolve(value); } rl.on('line', (line) => { if (done) return; const match = line.match(REGEX); if (match) { finish(parseElapsedTime(match[1])); rl.close(); stream.destroy(); } }); rl.on('close', () => finish(null)); rl.on('error', () => finish(null)); stream.on('error', () => finish(null)); }); } module.exports = { fullScan, scanResults, processResultDir };