172 lines
5.8 KiB
JavaScript
172 lines
5.8 KiB
JavaScript
|
|
'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 };
|